析构函数
当一个已初始化的 变量 或 临时变量 超出 作用域 时,它的 *析构函数* 会被运行,或者它会被 *丢弃*。赋值 也会运行其左侧操作数的析构函数(如果它已初始化)。如果一个变量只被部分初始化,则只会丢弃其已初始化的字段。
类型 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. }
丢弃作用域
每个变量或临时变量都与一个 *丢弃作用域* 相关联。当控制流离开丢弃作用域时,与该作用域相关联的所有变量都将按声明的相反顺序(对于变量)或创建的相反顺序(对于临时变量)丢弃。
丢弃作用域是在将 for
、if let
和 while let
表达式替换为使用 match
的等效表达式后确定的。重载运算符与内置运算符没有区别,并且不考虑 绑定模式。
给定一个函数或闭包,以下情况存在丢弃作用域
丢弃作用域按如下方式相互嵌套。当同时离开多个作用域时(例如从函数返回时),变量将从内向外丢弃。
- 整个函数作用域是最外层的作用域。
- 函数体代码块包含在整个函数的作用域内。
- 表达式语句中表达式的父级是语句的作用域。
let
语句 的初始化表达式的父级是let
语句的作用域。- 语句作用域的父级是包含该语句的代码块的作用域。
match
守卫表达式的父级是该守卫所属的分支的作用域。match
表达式中=>
之后表达式的父级是它所在的分支的作用域。- 分支作用域的父级是它所属的
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"); }
如果在 match
表达式的同一个分支中使用了多个模式,则将使用未指定的模式来确定丢弃顺序。
临时作用域
表达式的 *临时作用域* 是在 位置上下文 中使用时用于保存该表达式结果的临时变量的作用域,除非它被 提升。
除了生命周期延长之外,表达式的临时作用域是包含该表达式的最小作用域,并且是以下之一
- 整个函数。
- 一个语句。
if
、while
或loop
表达式的代码体。if
表达式的else
代码块。if
或while
表达式或match
守卫的条件表达式。- match 分支的代码体表达式。
- 惰性布尔表达式 的第二个操作数。
注意:
在函数体最后一个表达式中创建的临时变量在函数体中绑定的任何命名变量 *之后* 丢弃。它们的丢弃作用域是整个函数,因为没有更小的封闭临时作用域。
match
表达式的 scrutinee 不是一个临时作用域,因此 scrutinee 中的临时变量可以在match
表达式之后被丢弃。例如,match 1 { ref mut z => z };
中1
的临时变量的生命周期持续到语句结束。
一些例子
#![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!() }; // Dropped at the end of the statement (PrintOnDrop("first operand").0 == "" // Dropped at the ) || PrintOnDrop("second operand").0 == "") // Dropped at the end of the expression || PrintOnDrop("third operand").0 == ""; // Dropped at the end of the function, after local variables. // Changing this to a statement containing a return expression would make the // temporary be dropped before the local variables. Binding to a variable // which is then returned would also make the temporary be dropped first. match PrintOnDrop("Matched value in final expression") { // Dropped once the condition has been evaluated _ if PrintOnDrop("guard condition").0 == "" => (), _ => (), } }
操作数
在计算表达式的其他操作数时,也会创建临时变量来保存操作数的结果。临时变量与包含该操作数的表达式的作用域相关联。由于表达式求值后临时变量会被移走,因此丢弃它们不会产生任何影响,除非表达式中的一个操作数跳出表达式、返回或发生 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); }
如果 借用、解引用、字段 或 元组索引表达式 具有延长的临时作用域,则其操作数也具有延长的临时作用域。如果 索引表达式 具有延长的临时作用域,则被索引的表达式也具有延长的临时作用域。
基于模式的延长
*延长模式*是以下之一
因此 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
提供了一个包装器,用于防止自动丢弃变量或字段。
注意:即使变量的类型不是
'static
,通过std::mem::forget
或其他方式阻止运行析构函数也是安全的。除了本文档定义的保证运行析构函数的地方外,类型可能*不会*安全地依赖于运行析构函数来确保健全性。