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

有关临时作用域如何扩展的更多信息,请参阅临时作用域规则。 有关对尾表达式进行的类似更改,请参阅尾表达式临时作用域章节。

1

被检查表达式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 letmatch 的更改,并确定你需要的关于临时值何时被丢弃的行为。 如果你确定此更改是不必要的,那么你可以将更改恢复为 if let

如果你想手动检查这些警告而不执行版本迁移,你可以使用以下命令启用 lint:

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