析构器
当一个已初始化的变量或临时变量超出作用域时,它的析构器会被运行,或者说它会被 drop。赋值也会运行其左侧操作数的析构器(如果已初始化)。如果一个变量只被部分初始化,则只有已初始化的字段会被 drop。
类型 T
的析构器包含:
- 如果
T: Drop
,则调用<T as std::ops::Drop>::drop
- 递归地运行其所有字段的析构器。
如果析构器必须手动运行,例如在实现您自己的智能指针时,可以使用 std::ptr::drop_in_place
。
一些例子
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("{}", self.0); } } let mut overwritten = PrintOnDrop("drops when overwritten"); overwritten = PrintOnDrop("drops when scope ends"); let tuple = (PrintOnDrop("Tuple first"), PrintOnDrop("Tuple second")); let moved; // No destructor run on assignment. moved = PrintOnDrop("Drops when moved"); // Drops now, but is then uninitialized. moved; // Uninitialized does not drop. let uninitialized: PrintOnDrop; // After a partial move, only the remaining fields are dropped. let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten")); // Perform a partial move, leaving only `partial_move.0` initialized. core::mem::forget(partial_move.1); // When partial_move's scope ends, only the first field is dropped. }
Drop 作用域
每个变量或临时变量都与一个 drop 作用域 相关联。当控制流离开一个 drop 作用域时,所有与该作用域关联的变量都按照声明的相反顺序(对于变量)或创建的顺序(对于临时变量)被 drop。
Drop 作用域在将 for
、if let
和 while let
表达式替换为使用 match
的等效表达式后确定。
重载运算符与内置运算符没有区别,并且不考虑绑定模式。
给定一个函数或闭包,存在以下 drop 作用域:
- 整个函数
- 每个 语句
- 每个 表达式
- 每个代码块,包括函数体
- 在 块表达式 的情况下,代码块的作用域和表达式的作用域是同一个作用域。
match
表达式的每个分支
Drop 作用域彼此嵌套,如下所示。当一次离开多个作用域时,例如当从函数返回时,变量从内向外被 drop。
- 整个函数作用域是最外层的作用域。
- 函数体代码块包含在整个函数的作用域内。
- 表达式语句中表达式的父作用域是该语句的作用域。
let
语句的初始化器的父作用域是let
语句的作用域。
- 语句作用域的父作用域是包含该语句的代码块的作用域。
match
守卫表达式的父作用域是守卫所属分支的作用域。
match
表达式中=>
后的表达式的父作用域是它所在分支的作用域。
- 分支作用域的父作用域是它所属的
match
表达式的作用域。
- 所有其他作用域的父作用域是直接包围它的表达式的作用域。
函数参数的作用域
所有函数参数都在整个函数体的作用域内,因此在评估函数时最后被 drop。每个实际的函数参数在参数模式中引入的任何绑定之后被 drop。
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } // Drops `y`, then the second parameter, then `x`, then the first parameter fn patterns_in_parameters( (x, _): (PrintOnDrop, PrintOnDrop), (_, y): (PrintOnDrop, PrintOnDrop), ) {} // drop order is 3 2 0 1 patterns_in_parameters( (PrintOnDrop("0"), PrintOnDrop("1")), (PrintOnDrop("2"), PrintOnDrop("3")), ); }
局部变量的作用域
在 let
语句中声明的局部变量与包含 let
语句的代码块的作用域相关联。在 match
表达式中声明的局部变量与声明它们的 match
分支的分支作用域相关联。
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let declared_first = PrintOnDrop("Dropped last in outer scope"); { let declared_in_block = PrintOnDrop("Dropped in inner scope"); } let declared_last = PrintOnDrop("Dropped first in outer scope"); }
如果在 match
表达式的同一分支中使用多个模式,则将使用未指定的模式来确定 drop 顺序。
临时作用域
表达式的临时作用域是用于保存该表达式结果的临时变量的作用域,当在位置上下文中使用时,除非它被提升。
除了生命周期延长之外,表达式的临时作用域是包含该表达式的最小作用域,并且是以下之一:
- 整个函数。
- 一个语句。
if
、while
或loop
表达式的主体。if
表达式的else
代码块。if
或while
表达式的条件表达式,或match
守卫。- match 分支的主体表达式。
- 惰性布尔表达式的每个操作数。
if let
的模式匹配条件和结果主体(destructors.scope.temporary.edition2024)。- 代码块的尾表达式的全部内容(destructors.scope.temporary.edition2024)。
注释:
match
表达式的被匹配值不是临时作用域,因此被匹配值中的临时变量可以在match
表达式之后被 drop。例如,match 1 { ref mut z => z };
中的1
的临时变量在语句结束时才失效。
版本差异:2024 版本添加了两个新的临时作用域缩小规则:
if let
临时变量在else
代码块之前被 drop,并且代码块的尾表达式的临时变量在尾表达式评估后立即被 drop。
一些例子
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } let local_var = PrintOnDrop("local var"); // Dropped once the condition has been evaluated if PrintOnDrop("If condition").0 == "If condition" { // Dropped at the end of the block PrintOnDrop("If body").0 } else { unreachable!() }; if let "if let scrutinee" = PrintOnDrop("if let scrutinee").0 { PrintOnDrop("if let consequent").0 // `if let consequent` dropped here } // `if let scrutinee` is dropped here else { PrintOnDrop("if let else").0 // `if let else` dropped here }; // Dropped before the first || (PrintOnDrop("first operand").0 == "" // Dropped before the ) || PrintOnDrop("second operand").0 == "") // Dropped before the ; || PrintOnDrop("third operand").0 == ""; // Scrutinee is dropped at the end of the function, before local variables // (because this is the tail expression of the function body block). match PrintOnDrop("Matched value in final expression") { // Dropped once the condition has been evaluated _ if PrintOnDrop("guard condition").0 == "" => (), _ => (), } }
操作数
临时变量也被创建来保存表达式操作数的结果,同时评估其他操作数。临时变量与具有该操作数的表达式的作用域相关联。由于临时变量在表达式评估后会被移动,因此 drop 它们没有任何效果,除非表达式的操作数之一跳出表达式、返回或 panic。
#![allow(unused)] fn main() { struct PrintOnDrop(&'static str); impl Drop for PrintOnDrop { fn drop(&mut self) { println!("drop({})", self.0); } } loop { // Tuple expression doesn't finish evaluating so operands drop in reverse order ( PrintOnDrop("Outer tuple first"), PrintOnDrop("Outer tuple second"), ( PrintOnDrop("Inner tuple first"), PrintOnDrop("Inner tuple second"), break, ), PrintOnDrop("Never created"), ); } }
常量提升
当一个值表达式可以写成常量并被借用,并且该借用可以在最初编写表达式的位置被解引用,而不会改变运行时行为时,就会发生将值表达式提升到 'static
槽的情况。也就是说,提升的表达式可以在编译时进行评估,并且结果值不包含内部可变性或析构器(这些属性尽可能基于值来确定,例如 &None
始终具有类型 &'static Option<_>
,因为它不包含任何不允许的内容)。
临时生命周期延长
注意:临时生命周期延长的确切规则可能会发生变化。这里仅描述当前行为。
let
语句中表达式的临时作用域有时会延长到包含 let
语句的代码块的作用域。当通常的临时作用域太小时,会根据某些语法规则执行此操作。例如:
#![allow(unused)] fn main() { let x = &mut 0; // Usually a temporary would be dropped by now, but the temporary for `0` lives // to the end of the block. println!("{}", x); }
生命周期延长也适用于 static
和 const
项,在这些项中,它使临时变量在程序结束前一直存在。例如:
#![allow(unused)] fn main() { const C: &Vec<i32> = &Vec::new(); // Usually this would be a dangling reference as the `Vec` would only // exist inside the initializer expression of `C`, but instead the // borrow gets lifetime-extended so it effectively has `'static` lifetime. println!("{:?}", C); }
如果借用、解引用、字段或元组索引表达式具有延长的临时作用域,则其操作数也具有延长的临时作用域。如果索引表达式具有延长的临时作用域,则被索引的表达式也具有延长的临时作用域。
基于模式的延长
延长模式是以下之一:
因此,ref x
、V(ref x)
和 [ref x, y]
都是延长模式,但 x
、&ref x
和 &(ref x,)
不是。
如果 let
语句中的模式是延长模式,则初始化器表达式的临时作用域将被延长。
基于表达式的延长
对于带有初始化器的 let 语句,延长表达式是以下表达式之一:
因此,&mut 0
、(&1, &mut 2)
和 Some { 0: &mut 3 }
中的借用表达式都是延长表达式。 &0 + &1
和 Some(&mut 0)
中的借用不是:后者在语法上是函数调用表达式。
任何延长借用表达式的操作数都会使其临时作用域延长。
例子
以下是一些表达式具有延长临时作用域的示例:
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // The temporary that stores the result of `temp()` lives in the same scope // as x in these cases. let x = &temp(); let x = &temp() as &dyn Send; let x = (&*&temp(),); let x = { [Some { 0: &temp(), }] }; let ref x = temp(); let ref x = *&temp(); x; }
以下是一些表达式不具有延长临时作用域的示例:
#![allow(unused)] fn main() { fn temp() {} trait Use { fn use_temp(&self) -> &Self { self } } impl Use for () {} // The temporary that stores the result of `temp()` only lives until the // end of the let statement in these cases. let x = Some(&temp()); // ERROR let x = (&temp()).use_temp(); // ERROR x; }
不运行析构器
可以使用 std::mem::forget
来阻止变量的析构器运行,并且 std::mem::ManuallyDrop
提供了一个包装器来防止变量或字段被自动 drop。
注意:即使类型不是
'static
,通过std::mem::forget
或其他方式阻止析构器运行也是安全的。除了本文档定义析构器保证运行的地方之外,类型不能安全地依赖于析构器的运行以保证健全性。