析构函数
当一个已初始化的变量或临时值离开作用域时,其 析构函数 会运行,或者说它被 丢弃(dropped) 了。赋值操作也会运行其左操作数的析构函数(如果它已被初始化)。如果一个变量是部分初始化的,则只丢弃其已初始化的字段。
类型 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 scopes)
每个变量或临时值都关联到一个 丢弃作用域(drop scope)。当控制流离开一个丢弃作用域时,所有关联到该作用域的变量按照声明顺序(对于变量)或创建顺序(对于临时值)的逆序被丢弃。
丢弃作用域是在将 for
、if let
和 while let
表达式替换为其使用 match
的等价表达式之后确定的。
重载运算符与内置运算符没有区别,并且不考虑绑定模式。
给定一个函数或闭包,有以下几种丢弃作用域:
- 整个函数
- 每个语句
- 每个表达式
- 每个块,包括函数体
- 对于块表达式,块的作用域和表达式的作用域是同一个作用域。
match
表达式的每个分支(arm)
丢弃作用域之间存在以下嵌套关系。当同时离开多个作用域时,例如函数返回时,变量从内向外丢弃。
- 整个函数作用域是最外层的作用域。
- 函数体块包含在整个函数的作用域内。
- 表达式语句中表达式的父作用域是该语句的作用域。
let
语句初始化表达式的父作用域是该let
语句的作用域。
- 语句作用域的父作用域是包含该语句的块的作用域。
match
守卫表达式的父作用域是该守卫所属分支(arm)的作用域。
match
表达式中=>
之后表达式的父作用域是该表达式所在分支(arm)的作用域。
- 分支(arm)作用域的父作用域是该分支所属的
match
表达式的作用域。
- 所有其他作用域的父作用域是其紧邻的包含表达式的作用域。
函数参数的作用域
所有函数参数都在整个函数体作用域内,因此在评估函数时最后丢弃。每个实际的函数参数在其模式中引入的任何绑定之后丢弃。
#![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"); }
如果在同一个分支(arm)中使用了多个模式用于 match
表达式,则将使用未指定的模式来确定丢弃顺序。
临时作用域(Temporary scopes)
表达式的 临时作用域 是当表达式用于place context时,用于保存该表达式结果的临时变量的作用域,除非它被常量提升了。
除了生命周期延长之外,表达式的临时作用域是包含该表达式的最小作用域,并且是以下之一:
- 整个函数。
- 一个语句。
if
、while
或loop
表达式的主体。if
表达式的else
块。if
或while
表达式的条件表达式,或match
守卫。match
分支的主体表达式。- 惰性布尔表达式的每个操作数。
if let
的模式匹配条件和结果主体(destructors.scope.temporary.edition2024)。- 块的尾部表达式的整体(destructors.scope.temporary.edition2024)。
注意
match
表达式的检查表达式(scrutinee)不是临时作用域,因此检查表达式中的临时值可以在match
表达式之后丢弃。例如,match 1 { ref mut z => z };
中1
的临时值会一直存活到语句结束。
版本差异:2024 版本增加了两个新的临时作用域缩小规则:
if let
的临时值在else
块之前丢弃,块的尾部表达式的临时值在尾部表达式评估后立即丢弃。
一些例子
#![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 == "" => (), _ => (), } }
操作数
在评估其他操作数时,也会创建临时值来保存表达式操作数的结果。这些临时值关联到包含该操作数的表达式的作用域。由于一旦表达式评估完成,这些临时值就会被 move 走,所以丢弃它们没有效果,除非表达式的一个操作数跳出表达式、返回或发生恐慌(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"), ); } }
常量提升(Constant promotion)
值表达式提升到 'static
位置发生于当表达式可以写在常量中并被借用,并且该借用可以在表达式最初被写的位置被解引用,而不会改变运行时行为时。也就是说,提升的表达式可以在编译时评估,并且结果值不包含内部可变性(interior mutability)或析构函数(destructors)(这些属性在可能的情况下基于值确定,例如 &None
总是具有类型 &'static Option<_>
,因为它不包含任何不允许的内容)。
临时生命周期延长(Temporary lifetime extension)
注意
临时生命周期延长的具体规则可能会发生变化。这里仅描述当前的行为。
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); }
如果借用、解引用、字段访问或元组索引表达式具有延长的临时作用域,则其操作数也具有延长的临时作用域。如果索引表达式具有延长的临时作用域,则被索引的表达式也具有延长的临时作用域。
基于模式的延长
延长模式(extending pattern) 是指以下任一情况:
因此 ref x
、V(ref x)
和 [ref x, y]
都是延长模式,而 x
、&ref x
和 &(ref x,)
则不是。
如果 let
语句中的模式是延长模式,则初始化表达式的临时作用域会延长。
基于表达式的延长
对于带有初始化表达式的 let 语句,延长表达式(extending expression) 是以下之一的表达式:
因此,&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
提供一个包装器来阻止变量或字段自动丢弃。
注意
即使一个变量的类型不是
'static
,通过std::mem::forget
或其他方式阻止其析构函数运行也是安全的。除了本文档定义保证运行析构函数的地方之外,类型 不能 为了健全性而安全地依赖于析构函数会被运行。
不带 unwinding 的进程终止
有一些方法可以在不unwinding的情况下终止进程,在这种情况下,析构函数将不会运行。
标准库提供了std::process::exit
和std::process::abort
来显式执行此操作。此外,如果恐慌处理程序(panic handler)设置为 abort
,则发生恐慌将始终终止进程而不运行析构函数。
还有一个额外的情况需要注意:当恐慌到达非 unwinding 的 ABI 边界时,要么没有析构函数会运行,要么只有直到 ABI 边界的所有析构函数会运行。