尾表达式临时作用域
概要
详情
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 版本中,局部变量 c
在 c.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
临时作用域 章节。
迁移
不幸的是,没有语义保留的重写来缩短尾表达式中临时值的生命周期1。tail_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)] }
详细信息记录在 RFC 3606 中