析构器
当一个已初始化的变量或临时变量超出作用域时,它的析构器会被运行,或者说它会被 drop。赋值也会运行其左侧操作数的析构器(如果已初始化)。如果一个变量只被部分初始化,则只有已初始化的字段会被 drop。
类型 T
的析构器包含:
- 如果
T: Drop
,则调用<T as std::ops::Drop>::drop
- 递归地运行其所有字段的析构器。
如果析构器必须手动运行,例如在实现您自己的智能指针时,可以使用 std::ptr::drop_in_place
。
一些例子
Drop 作用域
每个变量或临时变量都与一个 drop 作用域 相关联。当控制流离开一个 drop 作用域时,所有与该作用域关联的变量都按照声明的相反顺序(对于变量)或创建的顺序(对于临时变量)被 drop。
Drop 作用域在将 for
、if let
和 while let
表达式替换为使用 match
的等效表达式后确定。
重载运算符与内置运算符没有区别,并且不考虑绑定模式。
给定一个函数或闭包,存在以下 drop 作用域:
- 整个函数
- 每个 语句
- 每个 表达式
- 每个代码块,包括函数体
- 在 块表达式 的情况下,代码块的作用域和表达式的作用域是同一个作用域。
match
表达式的每个分支
Drop 作用域彼此嵌套,如下所示。当一次离开多个作用域时,例如当从函数返回时,变量从内向外被 drop。
- 整个函数作用域是最外层的作用域。
- 函数体代码块包含在整个函数的作用域内。
- 表达式语句中表达式的父作用域是该语句的作用域。
let
语句的初始化器的父作用域是let
语句的作用域。
- 语句作用域的父作用域是包含该语句的代码块的作用域。
match
守卫表达式的父作用域是守卫所属分支的作用域。
match
表达式中=>
后的表达式的父作用域是它所在分支的作用域。
- 分支作用域的父作用域是它所属的
match
表达式的作用域。
- 所有其他作用域的父作用域是直接包围它的表达式的作用域。
函数参数的作用域
所有函数参数都在整个函数体的作用域内,因此在评估函数时最后被 drop。每个实际的函数参数在参数模式中引入的任何绑定之后被 drop。
局部变量的作用域
在 let
语句中声明的局部变量与包含 let
语句的代码块的作用域相关联。在 match
表达式中声明的局部变量与声明它们的 match
分支的分支作用域相关联。
如果在 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。
一些例子
操作数
临时变量也被创建来保存表达式操作数的结果,同时评估其他操作数。临时变量与具有该操作数的表达式的作用域相关联。由于临时变量在表达式评估后会被移动,因此 drop 它们没有任何效果,除非表达式的操作数之一跳出表达式、返回或 panic。
常量提升
当一个值表达式可以写成常量并被借用,并且该借用可以在最初编写表达式的位置被解引用,而不会改变运行时行为时,就会发生将值表达式提升到 'static
槽的情况。也就是说,提升的表达式可以在编译时进行评估,并且结果值不包含内部可变性或析构器(这些属性尽可能基于值来确定,例如 &None
始终具有类型 &'static Option<_>
,因为它不包含任何不允许的内容)。
临时生命周期延长
注意:临时生命周期延长的确切规则可能会发生变化。这里仅描述当前行为。
let
语句中表达式的临时作用域有时会延长到包含 let
语句的代码块的作用域。当通常的临时作用域太小时,会根据某些语法规则执行此操作。例如:
生命周期延长也适用于 static
和 const
项,在这些项中,它使临时变量在程序结束前一直存在。例如:
如果借用、解引用、字段或元组索引表达式具有延长的临时作用域,则其操作数也具有延长的临时作用域。如果索引表达式具有延长的临时作用域,则被索引的表达式也具有延长的临时作用域。
基于模式的延长
延长模式是以下之一:
因此,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)
中的借用不是:后者在语法上是函数调用表达式。
任何延长借用表达式的操作数都会使其临时作用域延长。
例子
以下是一些表达式具有延长临时作用域的示例:
以下是一些表达式不具有延长临时作用域的示例:
不运行析构器
可以使用 std::mem::forget
来阻止变量的析构器运行,并且 std::mem::ManuallyDrop
提供了一个包装器来防止变量或字段被自动 drop。
注意:即使类型不是
'static
,通过std::mem::forget
或其他方式阻止析构器运行也是安全的。除了本文档定义析构器保证运行的地方之外,类型不能安全地依赖于析构器的运行以保证健全性。