if let
临时作用域
概要
- 在
if let $pat = $expr { .. } else { .. }
表达式中,从求值$expr
生成的临时值将在程序进入else
分支之前被丢弃,而不是之后。
详情
2024 Edition 更改了 if let
表达式的被检查表达式1 中临时值的 drop 作用域。 这旨在帮助减少临时值存活时间过长可能导致的意外行为。
在 2024 Edition 之前,临时值的生命周期可能会超出 if let
表达式本身。 例如
#![allow(unused)] fn main() { // Before 2024 use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { if let Some(x) = *value.read().unwrap() { println!("value is {x}"); } else { let mut v = value.write().unwrap(); if v.is_none() { *v = Some(true); } } // <--- Read lock is dropped here in 2021 } }
在此示例中,调用 value.read()
生成的临时读取锁将不会被丢弃,直到 if let
表达式之后(即,在 else
代码块之后)。 如果执行 else
代码块,则当它尝试获取写入锁时,这将导致死锁。
2024 Edition 将临时值的生命周期缩短到 then 代码块完全求值完毕或者程序控制进入 else
代码块时。
#![allow(unused)] fn main() { // Starting with 2024 use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { if let Some(x) = *value.read().unwrap() { println!("value is {x}"); } // <--- Read lock is dropped here in 2024 else { let mut s = value.write().unwrap(); if s.is_none() { *s = Some(true); } } } }
有关临时作用域如何扩展的更多信息,请参阅临时作用域规则。 有关对尾表达式进行的类似更改,请参阅尾表达式临时作用域章节。
被检查表达式是 if let
表达式中正在匹配的表达式。
迁移
始终可以使用 match
重写 if let
,这是安全的。 match
被检查表达式的临时值会扩展到 match
表达式结束后(通常是语句结尾),这与 if let
的 2021 行为相同。
if_let_rescope
lint 在由于此更改而出现生命周期问题时,或者当 lint 检测到从 if let
的被检查表达式生成具有自定义的、非平凡 Drop
析构函数的临时值时,会建议修复方法。 例如,当接受 cargo fix
的建议时,可以将之前的示例重写为以下内容
#![allow(unused)] fn main() { use std::sync::RwLock; fn f(value: &RwLock<Option<bool>>) { match *value.read().unwrap() { Some(x) => { println!("value is {x}"); } _ => { let mut s = value.write().unwrap(); if s.is_none() { *s = Some(true); } } } // <--- Read lock is dropped here in both 2021 and 2024 } }
在这个特定的例子中,由于前面提到的死锁,这可能不是你想要的! 但是,某些场景可能假设临时值在 else
子句之后仍然存在,在这种情况下,你可能希望保留旧的行为。
if_let_rescope
lint 是 rust-2024-compatibility
lint 组的一部分,该组包含在自动版本迁移中。 为了迁移你的代码以与 Rust 2024 Edition 兼容,请运行
cargo fix --edition
迁移之后,建议你审查所有从 if let
到 match
的更改,并确定你需要的关于临时值何时被丢弃的行为。 如果你确定此更改是不必要的,那么你可以将更改恢复为 if let
。
如果你想手动检查这些警告而不执行版本迁移,你可以使用以下命令启用 lint:
#![allow(unused)] fn main() { // Add this to the root of your crate to do a manual migration. #![warn(if_let_rescope)] }