diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6d03c91..a16483e 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -114,7 +114,7 @@ - [Rust 2024](rust-2024/index.md) - [言語](rust-2024/language.md) - [RPIT lifetime capture rules](rust-2024/rpit-lifetime-capture.md) - - [`if let` temporary scope](rust-2024/temporary-if-let-scope.md) + - [`if let` の一時スコープ](rust-2024/temporary-if-let-scope.md) - [Tail expression temporary scope](rust-2024/temporary-tail-expr-scope.md) - [Match ergonomics reservations](rust-2024/match-ergonomics.md) - [Unsafe `extern` blocks](rust-2024/unsafe-extern.md) diff --git a/src/rust-2024/temporary-if-let-scope.md b/src/rust-2024/temporary-if-let-scope.md index 0a267d2..3c2a23a 100644 --- a/src/rust-2024/temporary-if-let-scope.md +++ b/src/rust-2024/temporary-if-let-scope.md @@ -1,17 +1,41 @@ -> **Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、[原文(英語版)](https://doc.rust-lang.org/nightly/edition-guide/introduction.html)をご参照ください。** - + + +# `if let` の一時スコープ + + +## 概要 + +- 式 `if let $pat = $expr { .. } else { .. }` において `$expr`を評価する際に作られる一時値は、`else` 節以降が実行された後ではなく、実行される前にドロップされます。 + + + +## 詳細 + +2024 エディションでは、`if let` 式の被検査体 (scurtinee)[^scurtinee]における[一時値 (temporary value)] のドロップスコープが変わります。 +これは、一時値の生存期間が長すぎることによる、ときに想定外の挙動を避けるためのものです。 + + + +2024 より前は、以下のように、一時値が `if let` 式自体の末尾まで生存し続けていました。 + + +```rust,edition2021 +// 2024 より前 +# use std::sync::RwLock; + +fn f(value: &RwLock>) { + 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` 節が開始するときに打ち切られます。 + +```rust,edition2024 +// 2024 以降 +# use std::sync::RwLock; + +fn f(value: &RwLock>) { + 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); + } + } +} +``` + + +一時スコープの範囲に関しての詳細は、[一時値のスコープ規則]をご参照ください。 +[末尾式の一時スコープ]の節では、末尾式に対する同様の変更について説明しています。 + + + +[^scurtinee]: [被検査体 (scurtinee)] とは、`if let` 式でマッチするかを検査される式(`=` 以降の式)のことです。 + +[被検査体 (scurtinee)]: https://doc.rust-lang.org/reference/glossary.html#scrutinee +[一時値 (temporary value)]: https://doc.rust-lang.org/reference/expressions.html#temporaries +[一時値のスコープ規則]: https://doc.rust-lang.org/reference/destructors.html#temporary-scopes +[末尾式の一時スコープ]: temporary-tail-expr-scope.md + + + +## 移行 + +`if let` はいつでも `match` に安全に書き換えられます。 +`match` の被検査体で生成される一時値は、2021 における `if let` の挙動と同様、少なくとも `match` 式の末尾まで(多くの場合、文の末尾まで)生存します。 + +[`if_let_rescope`] リントは、本変更によってライフタイムの問題が発生する場合や、`if let` の被検査体で生成される一時値が独自の非自明な `Drop` デストラクタをもつ場合に、修正案を提示します。 +例えば、前述の例に対して `cargo fix` の提案を適用した場合、以下のように書き換えられます。 + +```rust +# use std::sync::RwLock; +fn f(value: &RwLock>) { + 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 に移行するには、以下を実行します。 ```sh cargo fix --edition ``` + + +移行後、`if let` から `match` へ変更された箇所をチェックし、一時値がドロップされるタイミングを再確認することを推奨します。 +変更が不要と判断した場合は、`if let` へ戻すとよいでしょう。 + +エディション移行ツールを使わずに手動で確認したい場合は、以下のリントをオンにしてください。 + + + +```rust +// クレートのトップレベルに以下を追加すると手動移行できる +#![warn(if_let_rescope)] +``` + + +[`if_let_rescope`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#if-let-rescope