析构器

当一个已初始化变量临时变量超出作用域时,它的析构器会被运行,或者说它会被 drop赋值也会运行其左侧操作数的析构器(如果已初始化)。如果一个变量只被部分初始化,则只有已初始化的字段会被 drop。

类型 T 的析构器包含:

  1. 如果 T: Drop,则调用 <T as std::ops::Drop>::drop
  2. 递归地运行其所有字段的析构器。
    • 结构体的字段按照声明顺序被 drop。
    • 活跃的 枚举变体 的字段按照声明顺序被 drop。
    • 元组的字段按顺序被 drop。
    • 数组或拥有的 切片 的元素从第一个元素到最后一个元素被 drop。
    • 闭包通过移动捕获的变量以未指定的顺序被 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 作用域在将 forif letwhile 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 顺序。

临时作用域

表达式的临时作用域是用于保存该表达式结果的临时变量的作用域,当在位置上下文中使用时,除非它被提升

除了生命周期延长之外,表达式的临时作用域是包含该表达式的最小作用域,并且是以下之一:

注释:

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);
}

生命周期延长也适用于 staticconst 项,在这些项中,它使临时变量在程序结束前一直存在。例如:

#![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 xV(ref x)[ref x, y] 都是延长模式,但 x&ref x&(ref x,) 不是。

如果 let 语句中的模式是延长模式,则初始化器表达式的临时作用域将被延长。

基于表达式的延长

对于带有初始化器的 let 语句,延长表达式是以下表达式之一:

因此,&mut 0(&1, &mut 2)Some { 0: &mut 3 } 中的借用表达式都是延长表达式。 &0 + &1Some(&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 或其他方式阻止析构器运行也是安全的。除了本文档定义析构器保证运行的地方之外,类型不能安全地依赖于析构器的运行以保证健全性。