临时变量在语句末尾被删除,就像在 C++ 中一样。然而,IIRC,Rust 中的销毁顺序未指定(我们将在下面看到其后果),尽管当前的实现似乎只是以与构造相反的顺序删除值。
之间有很大的区别let _ = x;
and 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
例如,它的类型是&'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 无法推断这些对象的销毁顺序。该错误来自于 Rust 拒绝实例化B<'a>
与对象本身的生命周期B<'a>
实施Drop
(这条规则是由于以下原因而添加的RFC 769 https://github.com/rust-lang/rfcs/pull/769Rust 1.0 之前)。如果允许的话,drop
将能够访问已经被删除的值!然而,如果B<'a>
不执行Drop
,那么它是允许的,因为我们知道没有代码会尝试访问B
结构体被删除时的字段。