Skip to content

Latest commit

 

History

History
252 lines (204 loc) · 9.37 KB

temporary-if-let-scope.md

File metadata and controls

252 lines (204 loc) · 9.37 KB

if let の一時スコープ

概要

  • if let $pat = $expr { .. } else { .. } において $exprを評価する際に作られる一時値は、else 節以降が実行された後ではなく、実行される前にドロップされます。

詳細

2024 エディションでは、if let 式の被検査体 (scurtinee)1における一時値 (temporary value) のドロップスコープが変わります。 これは、一時値の生存期間が長すぎることによる、ときに想定外の挙動を避けるためのものです。

2024 より前は、以下のように、一時値が if let 式自体の末尾まで生存し続けていました。

// 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);
        }
    }
    // <--- 読み取りロックはここでドロップされる
}

この例では、value.read() によって生成された一時的な読み取りロックが、if let 式の末尾まで(else 節の末尾まで)ドロップされません。 else 節が実行された場合、書き込みロックを取得しようとしてデッドロックが発生してしまいます。

2024 エディションでは、一時値の生存期間は then 節(if let の直後の { } ブロック)の評価が完了したとき、あるいは else 節が開始するときに打ち切られます。

// 2024 以降
# use std::sync::RwLock;

fn f(value: &RwLock<Option<bool>>) {
    if let Some(x) = *value.read().unwrap() {
        println!("value is {x}");
    }
    // <--- 2024 では、読み取りロックはここでドロップされる
    else {
        let mut s = value.write().unwrap();
        if s.is_none() {
            *s = Some(true);
        }
    }
}

一時スコープの範囲に関しての詳細は、一時値のスコープ規則をご参照ください。 末尾式の一時スコープの節では、末尾式に対する同様の変更について説明しています。

移行

if let はいつでも match に安全に書き換えられます。 match の被検査体で生成される一時値は、2021 における if let の挙動と同様、少なくとも match 式の末尾まで(多くの場合、文の末尾まで)生存します。

if_let_rescope リントは、本変更によってライフタイムの問題が発生する場合や、if let の被検査体で生成される一時値が独自の非自明な Drop デストラクタをもつ場合に、修正案を提示します。 例えば、前述の例に対して cargo fix の提案を適用した場合、以下のように書き換えられます。

# 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);
            }
        }
    }
    // <--- Rust 2021 でも 2024 でも、読み取りロックはここでドロップされる
}

特に上記のコードでは、前述の通りデッドロックが起こるため望む挙動ではないでしょうが、場合によっては従来の挙動そのままに、一時値が else 節以降も生き延びることを意図することもあるでしょう。

if_let_rescope リントは、自動エディション移行に含まれる rust-2024-compatibility リントグループの一部です。 コードを Rust 2024 に移行するには、以下を実行します。

cargo fix --edition

移行後、if let から match へ変更された箇所をチェックし、一時値がドロップされるタイミングを再確認することを推奨します。 変更が不要と判断した場合は、if let へ戻すとよいでしょう。

エディション移行ツールを使わずに手動で確認したい場合は、以下のリントをオンにしてください。

// クレートのトップレベルに以下を追加すると手動移行できる
#![warn(if_let_rescope)]

Footnotes

  1. 被検査体 (scurtinee) とは、if let 式でマッチするかを検査される式(= 以降の式)のことです。