尾表达式临时作用域

概要

  • 在评估 函数 或闭包体,或 代码块 的尾表达式时生成的临时值,现在可能在局部变量之前被 drop,并且有时不会扩展到下一个更大的临时作用域。

详情

2024 版本更改了尾表达式中 临时值 的 drop 顺序。在 2024 版本之前,尾表达式中的临时值可以比代码块本身存活更久,并且比局部变量绑定更晚被 drop,这常常令人惊讶,如下例所示

#![allow(unused)]
fn main() {
// Before 2024
use std::cell::RefCell;
fn f() -> usize {
    let c = RefCell::new("..");
    c.borrow().len() // error[E0597]: `c` does not live long enough
}
}

这会在 2021 版本中产生以下错误

error[E0597]: `c` does not live long enough
 --> src/lib.rs:4:5
  |
3 |     let c = RefCell::new("..");
  |         - binding `c` declared here
4 |     c.borrow().len() // error[E0597]: `c` does not live long enough
  |     ^---------
  |     |
  |     borrowed value does not live long enough
  |     a temporary with access to the borrow is created here ...
5 | }
  | -
  | |
  | `c` dropped here while still borrowed
  | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>`
  |
  = note: the temporary is part of an expression at the end of a block;
          consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
  |
4 |     let x = c.borrow().len(); x // error[E0597]: `c` does not live long enough
  |     +++++++                 +++

For more information about this error, try `rustc --explain E0597`.

在 2021 版本中,局部变量 cc.borrow() 创建的临时值之前被 drop。2024 版本更改了这一点,使得临时值 c.borrow() 首先被 drop,然后是局部变量 c 被 drop,从而允许代码按预期编译。

临时作用域可能被缩小

当为了评估表达式而创建临时值时,临时值会根据 临时作用域规则 被 drop。这些规则定义了临时值将保持存活的时间长度。在 2024 版本之前,代码块的尾表达式中的临时值会扩展到代码块之外,到达下一个临时作用域边界。在许多情况下,这将是语句或函数体的末尾。在 2024 版本中,尾表达式的临时值现在可以在代码块末尾立即被 drop(在代码块中的任何局部变量之前)。

临时作用域的这种缩小可能会导致程序在 2024 版本中编译失败。例如

// This example works in 2021, but fails to compile in 2024.
fn main() {
    let x = { &String::from("1234") }.len();
}

在这个例子中,在 2021 版本中,临时 String 扩展到代码块之外,超过了对 len() 的调用,并在语句末尾被 drop。在 2024 版本中,它在代码块末尾立即被 drop,导致关于临时值在被借用时被 drop 的编译错误。

这些情况的解决方案是将代码块表达式提升到一个局部变量,以便临时值存活足够长的时间

fn main() {
    let s = { &String::from("1234") };
    let x = s.len();
}

这个特定的例子利用了 临时生命周期延长。临时生命周期延长是一组特定的规则,允许临时值比通常情况下存活更久。由于 String 临时值位于引用的后面,因此 String 临时值被延长到足以让下一个语句对其调用 len()

有关对 if let 表达式的临时作用域进行的类似更改,请参阅 if let 临时作用域 章节。

迁移

不幸的是,没有语义保留的重写来缩短尾表达式中临时值的生命周期1tail_expr_drop_order lint 检测是否在尾表达式中生成了具有自定义的、非平凡的 Drop 析构函数的临时值。当运行 cargo fix --edition 时,将出现来自此 lint 的警告,但在其他情况下不会自动进行任何更改。建议手动检查警告,并确定是否需要进行任何调整。

如果您想在不执行版本迁移的情况下手动检查这些警告,可以使用以下命令启用 lint

#![allow(unused)]
fn main() {
// Add this to the root of your crate to do a manual migration.
#![warn(tail_expr_drop_order)]
}
1

详细信息记录在 RFC 3606