2016-10-27 31 views
10

在C++中(如果错误,请纠正我),通过常量引用的临时绑定应该超过它所绑定的表达式。我认为Rust也是如此,但在两种不同的情况下,我得到了两种不同的行为。在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。

+0

这里有一些讨论,但仍然没有详细说明我相信虽然比以前更一致:https://github.com/rust-lang/rust/issues/32433 – WiSaGaN

回答

5

声明结束时临时删除,就像在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 BEnd of main.之后被打印。这表明B实例处于活动状态,直到函数结束,解释不同的销毁顺序。


现在,让我们看看如果我们修改B拿借来的指针用一生而不是原始指针会发生什么。其实,让我们去了一步,取出Drop实现了片刻:

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

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

编译没有问题。在幕后,Rust为A实例和B实例分配了相同的生命周期(即,如果我们参考了B实例,则其类型将为&'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实例和B实例被分配了相同的寿命,防锈不能推论这些对象的破坏秩序。这个错误来自于Rust拒绝实例化B<'a>与对象本身的生命周期,当B<'a>实现了Drop(该规则在Rust 1.0之前被添加为RFC 769的结果)。如果允许,drop将能够访问已经被删除的值!但是,如果B<'a>没有实现Drop,那么它是允许的,因为我们知道当结构被删除时没有代码会尝试访问B的字段。

+0

你也许还可以提到https: //github.com/rust-lang/rfcs/pull/769,因为这是为drop类型引入了“严格更大的生命周期”。 – Neikos

5

原始指针本身不携带任何类型的寿命,因此编译器可能会做这样的事情:

  1. 例子:

    • B被创建(以便它可以保持在*const A它)
    • A被创建
    • B没有绑定到一个绑定,从而被丢弃
    • A没有必要,因此被丢弃

让我们检查出的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)作为推定之前确实叫,所以你得到上面的输出。

  • 在本例中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)这样我们就得到你所看到的行为。