Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

析构器

已初始化 变量临时值离开作用域时,其析构器会被运行,或者说它被丢弃赋值也会运行其左操作数的析构器(如果它已被初始化)。如果变量已被部分初始化,则仅丢弃其已初始化的字段。

类型 T 的析构器包括:

  1. 如果 T: Drop,则调用 <T as core::ops::Drop>::drop
  2. 递归地运行其所有字段的析构器。
    • 结构体的字段按声明顺序丢弃。
    • 活动枚举变体的字段按声明顺序丢弃。
    • 元组的字段按顺序丢弃。
    • 数组或拥有所有权的切片的元素从第一个元素到最后一个按顺序丢弃。
    • 闭包通过 move 捕获的变量按未指定顺序丢弃。
    • Trait 对象运行底层类型的析构器。
    • 其他类型不会导致任何进一步的丢弃。

如果必须手动运行析构器(例如在实现你自己的智能指针时),可以使用 core::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;
// 赋值时不会运行析构器。
moved = PrintOnDrop("Drops when moved");
// 现在丢弃,但之后变为未初始化状态。
moved;

// 未初始化的值不会丢弃。
let uninitialized: PrintOnDrop;

// 部分移动后,只有剩余字段被丢弃。
let mut partial_move = (PrintOnDrop("first"), PrintOnDrop("forgotten"));
// 执行部分移动,仅保留 `partial_move.0` 初始化状态。
core::mem::forget(partial_move.1);
// 当 partial_move 的作用域结束时,只有第一个字段被丢弃。
}

丢弃作用域

每个变量或临时值都关联到一个丢弃作用域。当控制流离开丢弃作用域时,与该作用域关联的所有变量按声明(对于变量)或创建(对于临时值)的逆序被丢弃。

丢弃作用域可以通过将 forifwhile 表达式替换为使用 matchloopbreak 的等效表达式来确定。

重载运算符与内置运算符不做区分,且不考虑绑定模式

对于函数或闭包,存在以下丢弃作用域:

  • 整个函数
  • 每个块,包括函数体
    • 对于块表达式,该块的作用域和该表达式的作用域是同一个作用域。
  • 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);
    }
}
// 丢弃 `y`,然后丢弃第二个参数,然后丢弃 `x`,然后丢弃第一个参数
fn patterns_in_parameters(
    (x, _): (PrintOnDrop, PrintOnDrop),
    (_, y): (PrintOnDrop, PrintOnDrop),
) {}

// 丢弃顺序为 3 2 0 1
patterns_in_parameters(
    (PrintOnDrop("0"), PrintOnDrop("1")),
    (PrintOnDrop("2"), PrintOnDrop("3")),
);
}

局部变量的作用域

let 语句中声明的局部变量关联到包含该 let 语句的块的作用域。

#![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 表达式或模式匹配的 match 守卫中声明的局部变量关联到它们所在 match 分支的分支作用域。

#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
match PrintOnDrop("Dropped last in the first arm's scope") {
    // 当守卫求值成功时,控制流保留在分支中,
    // 值可以从被检查值移动到分支的绑定中,
    // 导致它们在分支作用域中被丢弃。
    x if let y = PrintOnDrop("Dropped second in the first arm's scope")
        && let z = PrintOnDrop("Dropped first in the first arm's scope") =>
    {
        let declared_in_block = PrintOnDrop("Dropped in inner scope");
        // 模式匹配守卫的绑定和临时值按逆序丢弃,
        // 在丢弃每个守卫条件操作数的临时值之前先丢弃其绑定。
        // 最后,丢弃由分支模式绑定的变量。
    }
    _ => unreachable!(),
}

match PrintOnDrop("Dropped in the enclosing temporary scope") {
    // 当守卫求值失败时,控制流离开分支作用域,
    // 导致更早的模式匹配守卫条件操作数的绑定和临时值被丢弃。
    // 这发生在求值下一个分支的守卫或主体之前。
    _ if let y = PrintOnDrop("Dropped in the first arm's scope")
        && false => unreachable!(),
    // 当由于自重叠或模式导致守卫多次执行时,
    // 控制流在守卫失败时离开分支作用域,
    // 并在再次执行守卫之前重新进入分支作用域。
    _ | _ if let y = PrintOnDrop("Dropped in the second arm's scope twice")
        && false => unreachable!(),
    _ => {},
}
}

模式中的变量按模式内声明的逆序丢弃。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
let (declared_first, declared_last) = (
    PrintOnDrop("Dropped last"),
    PrintOnDrop("Dropped first"),
);
}

对于丢弃顺序,或模式按第一个子模式中给出的顺序声明绑定。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
// 在 `y` 之前丢弃 `x`。
fn or_pattern_drop_order<T>(
    (Ok([x, y]) | Err([y, x])): Result<[T; 2], [T; 2]>
//   ^^^^^^^^^^   ^^^^^^^^^^^ 这是第二个子模式。
//   |
//   这是第一个子模式。
//
//   在第一个子模式中,`x` 在 `y` 之前声明。由于它是
//   第一个子模式,即使匹配到绑定顺序相反的
//   第二个子模式时,也使用该顺序。
) {}

// 这里我们匹配第一个子模式,丢弃顺序按照
// 第一个子模式中的声明顺序。
or_pattern_drop_order(Ok([
    PrintOnDrop("Declared first, dropped last"),
    PrintOnDrop("Declared last, dropped first"),
]));

// 这里我们匹配第二个子模式,丢弃顺序仍然
// 按照第一个子模式中的声明顺序。
or_pattern_drop_order(Err([
    PrintOnDrop("Declared last, dropped first"),
    PrintOnDrop("Declared first, dropped last"),
]));
}

临时值作用域

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

除了生命周期延长的情况外,表达式的临时值作用域是包含该表达式且符合以下条件之一的最小作用域:

Note

match 表达式的被检查值不是一个临时值作用域,因此被检查值中的临时值可以在 match 表达式之后被丢弃。例如,在 match 1 { ref mut z => z }; 中,1 的临时值存活到语句结束。

Note

解构赋值的脱糖限制了其所赋值操作数(RHS)的临时值作用域。详情请参见 expr.assign.destructure.tmp-scopes

2024 Edition differences

2024 版添加了两条新的临时值作用域收窄规则:if let 的临时值在 else 块之前丢弃,块尾部表达式的临时值在尾部表达式求值后立即丢弃。

一些示例:

#![allow(unused)]
fn main() {
#![allow(irrefutable_let_patterns)]
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
let local_var = PrintOnDrop("local var");

// 条件求值后立即丢弃
if PrintOnDrop("If condition").0 == "If condition" {
    // 在块末尾丢弃
    PrintOnDrop("If body").0
} else {
    unreachable!()
};

if let "if let scrutinee" = PrintOnDrop("if let scrutinee").0 {
    PrintOnDrop("if let consequent").0
    // `if let consequent` 在这里丢弃
}
// `if let scrutinee` 在这里丢弃
else {
    PrintOnDrop("if let else").0
    // `if let else` 在这里丢弃
};

while let x = PrintOnDrop("while let scrutinee").0 {
    PrintOnDrop("while let loop body").0;
    break;
    // `while let loop body` 在这里丢弃。
    // `while let scrutinee` 在这里丢弃。
}

// 在第一个 || 之前丢弃
(PrintOnDrop("first operand").0 == ""
// 在 ) 之前丢弃
|| PrintOnDrop("second operand").0 == "")
// 在分号之前丢弃
|| PrintOnDrop("third operand").0 == "";

// 被检查值在函数末尾丢弃,早于局部变量
// (因为这是函数体块的尾部表达式)。
match PrintOnDrop("Matched value in final expression") {
    // 非模式匹配守卫的临时值在条件求值后丢弃
    _ if PrintOnDrop("guard condition").0 == "" => (),
    // 模式匹配守卫的临时值在离开分支作用域时丢弃
    _ if let "guard scrutinee" = PrintOnDrop("guard scrutinee").0 => {
        let _ = &PrintOnDrop("lifetime-extended temporary in inner scope");
        // `lifetime-extended temporary in inner scope` 在这里丢弃
    }
    // `guard scrutinee` 在这里丢弃
    _ => (),
}
}

操作数

临时值也会被创建以在计算其他操作数时保存表达式操作数的结果。这些临时值关联到具有该操作数的表达式的作用域。由于一旦表达式求值完成,这些临时值就被移走,因此丢弃它们没有效果,除非某个表达式操作数中断、返回或 panic 导致了提前退出。

#![allow(unused)]
fn main() {
struct PrintOnDrop(&'static str);
impl Drop for PrintOnDrop {
    fn drop(&mut self) {
        println!("drop({})", self.0);
    }
}
loop {
    // 元组表达式未完成求值,因此操作数以逆序丢弃
    (
        PrintOnDrop("Outer tuple first"),
        PrintOnDrop("Outer tuple second"),
        (
            PrintOnDrop("Inner tuple first"),
            PrintOnDrop("Inner tuple second"),
            break,
        ),
        PrintOnDrop("Never created"),
    );
}
}

常量提升

当值表达式可以写为常量并被借用,且该借用可以在原本书写表达式的位置被解引用而不改变运行时行为时,该表达式可以被提升到 'static 槽位。也就是说,提升后的表达式可以在编译时求值,其结果值不包含内部可变性析构器(这些属性在可能的情况下根据值确定,例如 &None 始终具有类型 &'static Option<_>,因为它不包含任何禁止的内容)。

临时值生命周期延长

Note

临时值生命周期延长的确切规则可能会变更。这里仅描述当前行为。

let 语句中表达式的临时值作用域有时被延长到包含该 let 语句的块的作用域。当通常的临时值作用域太小时,基于某些语法规则会执行此延长操作。例如:

#![allow(unused)]
fn main() {
let x = &mut 0;
// 通常临时值现在已经丢弃,但 `0` 的临时值存活到块的末尾。
println!("{}", x);
}

生命周期延长也适用于 staticconst 项,使临时值存活到程序结束。例如:

#![allow(unused)]
fn main() {
const C: &Vec<i32> = &Vec::new();
// 通常这会是一个悬垂引用,因为 `Vec` 只存在于
// `C` 的初始化器表达式中,但借用的生命周期被延长,
// 因此它实际上具有 `'static` 生命周期。
println!("{:?}", C);
}

如果借用解引用字段元组索引表达式具有延长的临时值作用域,则其操作数也具有延长的临时值作用域。如果索引表达式具有延长的临时值作用域,则被索引的表达式也具有延长的临时值作用域。

基于模式的延长

扩展模式是以下之一:

  • 通过引用或可变引用进行绑定的标识符模式

    #![allow(unused)]
    fn main() {
    fn temp() {}
    let ref x = temp(); // 通过引用绑定。
    x;
    let ref mut x = temp(); // 通过可变引用绑定。
    x;
    }
  • 其中至少一个直接子模式是扩展模式的结构体元组元组结构体切片或模式

    #![allow(unused)]
    fn main() {
    use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
    static X: AtomicU64 = AtomicU64::new(0);
    struct W<T>(T);
    impl<T> Drop for W<T> { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
    let W { 0: ref x } = W(()); // 结构体模式。
    x;
    let W(ref x) = W(()); // 元组结构体模式。
    x;
    let (W(ref x),) = (W(()),); // 元组模式。
    x;
    let [W(ref x), ..] = [W(())]; // 切片模式。
    x;
    let (Ok(W(ref x)) | Err(&ref x)) = Ok(W(())); // 或模式。
    x;
    //
    // 以上所有临时值在这里仍然存活。
    assert_eq!(0, X.load(Relaxed));
    }

因此 ref xV(ref x)[ref x, y] 都是扩展模式,但 x&ref x&(ref x,) 不是。

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

#![allow(unused)]
fn main() {
fn temp() {}
// 这是一个扩展模式,因此临时值作用域被延长。
let ref x = *&temp(); // OK
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// 这既不是扩展模式也不是扩展表达式,
// 因此临时值在分号处被丢弃。
let &ref x = *&&temp(); // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// 这不是扩展模式,但它是扩展表达式,
// 因此临时值存活到 `let` 语句之后。
let &ref x = &*&temp(); // OK
x;
}

基于表达式的延长

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

Note

解构赋值的脱糖使其所赋值操作数(RHS)成为新引入块中的扩展表达式。详情请参见 expr.assign.destructure.tmp-ext

因此 &mut 0(&1, &mut 2)Some(&mut 3) 中的借用表达式都是扩展表达式。&0 + &1f(&mut 0) 中的借用则不是。

扩展借用表达式的操作数的临时值作用域延长

扩展超级宏调用表达式的超级操作数作用域延长

Note

rustc 不将扩展数组表达式的数组重复操作数视为扩展表达式。是否应该这样处理是一个开放性问题。

详情请参见 Rust issue #146092

示例

以下是表达式具有延长临时值作用域的一些示例:

#![allow(unused)]
fn main() {
use core::pin::pin;
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
static X: AtomicU64 = AtomicU64::new(0);
#[derive(Debug)] struct S;
impl Drop for S { fn drop(&mut self) { X.fetch_add(1, Relaxed); } }
const fn temp() -> S { S }
let x = &temp(); // 借用的操作数。
x;
let x = &raw const *&temp(); // 裸借用的操作数。
assert_eq!(X.load(Relaxed), 0);
let x = &temp() as &dyn Send; // cast 的操作数。
x;
let x = (&*&temp(),); // 元组构造器的操作数。
x;
struct W<T>(T);
let x = W(&temp()); // 元组结构体构造器的参数。
x;
let x = Some(&temp()); // 元组枚举变体构造器的参数。
x;
let x = { [Some(&temp())] }; // 块的最终表达式。
x;
let x = const { &temp() }; // `const` 块的最终表达式。
x;
let x = unsafe { &temp() }; // `unsafe` 块的最终表达式。
x;
let x = if true { &temp() } else { &temp() };
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//           `if`/`else` 块的最终表达式。
x;
let x = match () { _ => &temp() }; // `match` 分支表达式。
x;
let x = pin!(temp()); // 超级宏调用表达式的超级操作数。
x;
let x = pin!({ &mut temp() }); // 同上。
x;
let x = format_args!("{:?}", temp()); // 同上。
x;
//
// 以上所有临时值在这里仍然存活。
assert_eq!(0, X.load(Relaxed));
}

以下是没有延长临时值作用域的表达式的一些示例:

#![allow(unused)]
fn main() {
fn temp() {}
// 函数调用的参数不是扩展表达式。临时值在分号处丢弃。
let x = core::convert::identity(&temp()); // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
trait Use { fn use_temp(&self) -> &Self { self } }
impl Use for () {}
// 方法调用的接收者不是扩展表达式。
let x = (&temp()).use_temp(); // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// match 表达式的被检查值不是扩展表达式。
let x = match &temp() { x => x }; // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// `async` 块的最终表达式不是扩展表达式。
let x = async { &temp() }; // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// 闭包的最终表达式不是扩展表达式。
let x = || &temp(); // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// 循环 break 的操作数不是扩展表达式。
let x = loop { break &temp() }; // 错误
x;
}
#![allow(unused)]
fn main() {
fn temp() {}
// break 到标签的操作数不是扩展表达式。
let x = 'a: { break 'a &temp() }; // 错误
x;
}
#![allow(unused)]
fn main() {
use core::pin::pin;
fn temp() {}
// `pin!` 的参数仅在调用是扩展表达式时才被作为扩展表达式。
// 由于它不是,因此内部块不是扩展表达式,所以其尾部
// 表达式中的临时值被立即丢弃。
pin!({ &temp() }); // 错误
}
#![allow(unused)]
fn main() {
fn temp() {}
// 同上。
format_args!("{:?}", { &temp() }); // 错误
}

不运行析构器

手动阻止析构器

core::mem::forget 可用于阻止变量的析构器运行,core::mem::ManuallyDrop 提供了一个包装器来防止变量或字段被自动丢弃。

Note

通过 core::mem::forget 或其他方式阻止析构器运行是安全的,即使变量的类型不是 'static。除了本文档定义的保证运行析构器的地方之外,类型不能安全地依赖析构器的运行来保证健全性。

不展开的进程终止

有一些终止进程的方式不会进行展开,在这种情况下析构器不会运行。

标准库提供了 std::process::exitstd::process::abort 来显式执行此操作。此外,如果 panic 处理器被设置为 abort,则 panic 将始终终止进程而不运行析构器。

还有一个需要知晓的额外情况:当 panic 到达不可展开的 ABI 边界时,要么没有析构器运行,要么直到 ABI 边界的所有析构器都运行。