在Rust中涉及临时的破坏令

时间:2016-10-27 16:06:07

标签: rust temporary-objects

在C ++中(如果错误请纠正我),通过常量引用的临时绑定应该比它绑定的表达式更长。我认为在Rust中也是如此,但我在两种不同的情况下得到了两种不同的行为。

考虑:

struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }

struct B(*const A);
impl Drop for B { fn drop(&mut self) { println!("Drop B.") } }

fn main() {
    let _ = B(&A as *const A); // B is destroyed after this expression itself.
}

输出结果为:

Drop B.
Drop A.

这是你所期望的。但现在如果你这样做:

fn main() {
    let _b = B(&A as *const A); // _b will be dropped when scope exits main()
}

输出结果为:

Drop A.
Drop B.

这不是我的预期。

这是否有意,如果是,那么两种情况下行为差异的理由是什么?

我正在使用Rust 1.12.1。

2 个答案:

答案 0 :(得分:6)

临时语句在语句结束时被删除,就像在C ++中一样。然而,IIRC,Rust中的破坏顺序没有具体说明(我们将看到下面的结果),尽管当前的实现似乎只是按照相反的构造顺序丢弃值。

let _ = x;let _b = x;之间存在很大差异。 _不是Rust中的标识符:它是一个通配符模式。由于这种模式没有找到任何变量,因此在语句结束时有效地删除了最终值。

另一方面,_b是一个标识符,因此该值绑定到具有该名称的变量,该变量将其生命周期延长到函数结束。但是,A实例仍然是临时的,因此它将在语句结束时删除(我相信C ++会做同样的事情)。由于语句的结尾是在函数结束之前,因此首先删除A实例,然后删除B实例。

为了更清楚,让我们在main中添加另一个语句:

fn main() {
    let _ = B(&A as *const A);
    println!("End of main.");
}

这会产生以下输出:

Drop B.
Drop A.
End of main.

到目前为止一切顺利。现在让我们试试let _b;输出是:

Drop A.
End of main.
Drop B.

正如我们所见,Drop B是在End of main.之后打印的。这表明B实例在函数结束前仍处于活动状态,解释了不同的销毁顺序。

现在,让我们看看如果我们修改B以使用带有生命周期的借用指针而不是原始指针会发生什么。实际上,让我们更进一步,暂时删除Drop实现:

struct A;
struct B<'a>(&'a A);

fn main() {
    let _ = B(&A);
}

编译好。在幕后,Rust为A实例和B实例分配相同的生命周期(即如果我们引用了B实例,则其类型为{{1}两个&'a B<'a>都是完全相同的生命周期)。当两个值具有相同的生命周期时,则必然我们需要将其中一个丢弃到另一个之前,并且如上所述,订单未指定。如果我们添加'a实现会怎样?

Drop

现在我们收到了编译错误:

struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }

struct B<'a>(&'a A);
impl<'a> Drop for B<'a> { fn drop(&mut self) { println!("Drop B.") } }

fn main() {
    let _ = B(&A);
}

由于error: borrowed value does not live long enough --> <anon>:8:16 | 8 | let _ = B(&A); | ^ does not live long enough | note: reference must be valid for the destruction scope surrounding statement at 8:4... --> <anon>:8:5 | 8 | let _ = B(&A); | ^^^^^^^^^^^^^^ note: ...but borrowed value is only valid for the statement at 8:4 --> <anon>:8:5 | 8 | let _ = B(&A); | ^^^^^^^^^^^^^^ help: consider using a `let` binding to increase its lifetime --> <anon>:8:5 | 8 | let _ = B(&A); | ^^^^^^^^^^^^^^ 实例和A实例都被分配了相同的生命周期,因此Rust无法推断这些对象的销毁顺序。该错误来自于当B实现B<'a>时,Rust拒绝使用对象本身的生命周期来实例化B<'a>(此规则是RFC 769之前添加的结果Rust 1.0)。如果允许,Drop将能够访问已经删除的值!但是,如果drop没有实施B<'a>,那么就允许这样做,因为我们知道没有代码会尝试访问Drop的字段时结构被删除。

答案 1 :(得分:5)

原始指针本身不具有任何生命周期,因此编译器可能会执行以下操作:

  1. 示例:

    • 创建了B(因此它可以容纳*const A
    • 创建了
    • B未绑定到绑定因此被删除
    • 不需要A,因此被删除
  2. 我们来看看MIR:

    fn main() -> () {
        let mut _0: ();                      // return pointer
        let mut _1: B;
        let mut _2: *const A;
        let mut _3: *const A;
        let mut _4: &A;
        let mut _5: &A;
        let mut _6: A;
        let mut _7: ();
    
        bb0: {
            StorageLive(_1);                 // scope 0 at <anon>:8:13: 8:30
            StorageLive(_2);                 // scope 0 at <anon>:8:15: 8:29
            StorageLive(_3);                 // scope 0 at <anon>:8:15: 8:17
            StorageLive(_4);                 // scope 0 at <anon>:8:15: 8:17
            StorageLive(_5);                 // scope 0 at <anon>:8:15: 8:17
            StorageLive(_6);                 // scope 0 at <anon>:8:16: 8:17
            _6 = A::A;                       // scope 0 at <anon>:8:16: 8:17
            _5 = &_6;                        // scope 0 at <anon>:8:15: 8:17
            _4 = &(*_5);                     // scope 0 at <anon>:8:15: 8:17
            _3 = _4 as *const A (Misc);      // scope 0 at <anon>:8:15: 8:17
            _2 = _3;                         // scope 0 at <anon>:8:15: 8:29
            _1 = B::B(_2,);                  // scope 0 at <anon>:8:13: 8:30
            drop(_1) -> bb1;                 // scope 0 at <anon>:8:31: 8:31
        }
    
        bb1: {
            StorageDead(_1);                 // scope 0 at <anon>:8:31: 8:31
            StorageDead(_2);                 // scope 0 at <anon>:8:31: 8:31
            StorageDead(_3);                 // scope 0 at <anon>:8:31: 8:31
            StorageDead(_4);                 // scope 0 at <anon>:8:31: 8:31
            StorageDead(_5);                 // scope 0 at <anon>:8:31: 8:31
            drop(_6) -> bb2;                 // scope 0 at <anon>:8:31: 8:31
        }
    
        bb2: {
            StorageDead(_6);                 // scope 0 at <anon>:8:31: 8:31
            _0 = ();                         // scope 0 at <anon>:7:11: 9:2
            return;                          // scope 0 at <anon>:9:2: 9:2
        }
    }
    

    正如我们所看到的那样drop(_1)确实在假定drop(_6)之前被调用,因此你得到了上面的输出。

    1. 实施例
    2. 在此示例中,B绑定到绑定

      • 创建了B(出于与上述相同的原因)
      • 创建了
      • A未绑定并被删除
      • B超出范围并被放弃

      相应的MIR:

      fn main() -> () {
          let mut _0: ();                      // return pointer
          scope 1 {
              let _1: B;                       // "b" in scope 1 at <anon>:8:9: 8:10
          }
          let mut _2: *const A;
          let mut _3: *const A;
          let mut _4: &A;
          let mut _5: &A;
          let mut _6: A;
          let mut _7: ();
      
          bb0: {
              StorageLive(_1);                 // scope 0 at <anon>:8:9: 8:10
              StorageLive(_2);                 // scope 0 at <anon>:8:15: 8:29
              StorageLive(_3);                 // scope 0 at <anon>:8:15: 8:17
              StorageLive(_4);                 // scope 0 at <anon>:8:15: 8:17
              StorageLive(_5);                 // scope 0 at <anon>:8:15: 8:17
              StorageLive(_6);                 // scope 0 at <anon>:8:16: 8:17
              _6 = A::A;                       // scope 0 at <anon>:8:16: 8:17
              _5 = &_6;                        // scope 0 at <anon>:8:15: 8:17
              _4 = &(*_5);                     // scope 0 at <anon>:8:15: 8:17
              _3 = _4 as *const A (Misc);      // scope 0 at <anon>:8:15: 8:17
              _2 = _3;                         // scope 0 at <anon>:8:15: 8:29
              _1 = B::B(_2,);                  // scope 0 at <anon>:8:13: 8:30
              StorageDead(_2);                 // scope 0 at <anon>:8:31: 8:31
              StorageDead(_3);                 // scope 0 at <anon>:8:31: 8:31
              StorageDead(_4);                 // scope 0 at <anon>:8:31: 8:31
              StorageDead(_5);                 // scope 0 at <anon>:8:31: 8:31
              drop(_6) -> [return: bb3, unwind: bb2]; // scope 0 at <anon>:8:31: 8:31
          }
      
          bb1: {
              resume;                          // scope 0 at <anon>:7:1: 9:2
          }
      
          bb2: {
              drop(_1) -> bb1;                 // scope 0 at <anon>:9:2: 9:2
          }
      
          bb3: {
              StorageDead(_6);                 // scope 0 at <anon>:8:31: 8:31
              _0 = ();                         // scope 1 at <anon>:7:11: 9:2
              drop(_1) -> bb4;                 // scope 0 at <anon>:9:2: 9:2
          }
      
          bb4: {
              StorageDead(_1);                 // scope 0 at <anon>:9:2: 9:2
              return;                          // scope 0 at <anon>:9:2: 9:2
          }
      }
      

      我们可以看到drop(_6)drop(_1)之前被调用,因此我们得到了您所看到的行为。