【问题标题】:Destruction order involving temporaries in RustRust 中涉及临时对象的销毁顺序
【发布时间】:2016-10-27 16:06:07
【问题描述】:

在 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。

【问题讨论】:

标签: rust temporary-objects


【解决方案1】:

临时变量在语句的末尾被删除,就像在 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 实例,它的类型将是 &amp;'a B&lt;'a&gt; 其中 '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&lt;'a&gt; 实现 Drop 时,Rust 拒绝用对象本身的生命周期实例化 B&lt;'a&gt;(此规则是在 Rust 1.0 之前作为 RFC 769 的结果添加的)。如果允许,drop 将能够访问已删除的值!但是,如果B&lt;'a&gt; 没有实现Drop,那么它是允许的,因为我们知道当结构体被删除时,没有代码会尝试访问B 的字段。

【讨论】:

【解决方案2】:

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

  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) 之前被调用,因此你得到了上面的输出。

  1. 示例

在这个例子中 B 被绑定到一个绑定

  • B 被创建(原因同上)
  • A 已创建
  • 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) 之前被调用,所以我们得到了你所看到的行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-11
    • 2016-06-02
    • 2011-04-13
    • 2015-01-10
    • 1970-01-01
    • 2019-06-30
    • 2011-01-13
    • 2019-07-19
    相关资源
    最近更新 更多