diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6d03c91..9666841 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -115,7 +115,7 @@ - [言語](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) - - [Tail expression temporary scope](rust-2024/temporary-tail-expr-scope.md) + - [末尾式の一時スコープ](rust-2024/temporary-tail-expr-scope.md) - [Match ergonomics reservations](rust-2024/match-ergonomics.md) - [Unsafe `extern` blocks](rust-2024/unsafe-extern.md) - [Unsafe attributes](rust-2024/unsafe-attributes.md) diff --git a/src/rust-2024/temporary-tail-expr-scope.md b/src/rust-2024/temporary-tail-expr-scope.md index cc29411..618b8d6 100644 --- a/src/rust-2024/temporary-tail-expr-scope.md +++ b/src/rust-2024/temporary-tail-expr-scope.md @@ -1,20 +1,49 @@ -> **Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、[原文(英語版)](https://doc.rust-lang.org/nightly/edition-guide/introduction.html)をご参照ください。** - + + +# 末尾式の一時スコープ + + +## 概要 + +- [関数]・クロージャ本体・[ブロック]の末尾式の評価中に生成される一時値は、ローカル変数より先にドロップされるようになり、外側の一時スコープまで生き延びない場合があります。 + + + +[関数]: https://doc.rust-lang.org/reference/items/functions.html +[ブロック]: https://doc.rust-lang.org/reference/expressions/block-expr.html + + +## 詳細 + +2024 エディションから、[末尾式] [^1]のドロップ順序が変わります。 +それ以前の非直感的な挙動として、以下のように、末尾式内の一時値がブロックそのものより長生きして、ローカル変数束縛より後にドロップされるというものがありました。 + + + +[末尾式]: https://doc.rust-lang.org/reference/expressions.html#temporaries + +```rust,edition2021,compile_fail,E0597 +// 2024 より前 +# use std::cell::RefCell; +fn f() -> usize { + let c = RefCell::new(".."); + c.borrow().len() // error[E0597]: `c` does not live long enough + // (訳)エラー[E0597]: `c` は十分に長生きしません +} +``` + + + +このコードは、2021 エディションでは以下のようなエラーになりました。 ```text error[E0597]: `c` does not live long enough +((訳)エラー[E0597]: `c` は十分に長生きしません) --> src/lib.rs:4:5 | 3 | let c = RefCell::new(".."); | - binding `c` declared here + ((訳)束縛 `c` はここで定義されています) 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 + ((訳) `c` が借用中にここでドロップされています) | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>` | + ((訳)...ここでその一時値がドロップされて `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 +((訳)ヘルプ: 例えば、式の値を一旦新しいローカル変数 `x` に格納し、それをブロック末尾の式とするとよいです) | 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`. +((訳)詳細に関しては `rustc --explain E0597` をご参照ください。) ``` + + +2021 では、ローカル変数 `c` よりも `c.borrow()` で作られた一時値の方が先にドロップされます。 +2024 エディションでこの挙動は変わり、一時値 `c.borrow()` が先にドロップされてからローカル変数 `c` がドロップされるようになり、コードが期待通りコンパイルされるよになりました。 + +[^1]: 訳注: 関数・クロージャ・ブロックの `{ }` の末尾にある、セミコロン `;` を伴わない式のこと。 + +### 一時スコープ縮小の可能性 + + + +式の評価のために一時値が作られるとき、一時値は[一時スコープのルール]に則ってドロップされます。 +このルールは一時値の生存期間を規定するものです。 +2024 より前では、ブロックの末尾式における一時値はそのブロックに収まらず次のスコープ境界まで、もっぱら文・関数の末尾まで生存していました。 +2024 以降では、末尾式内の一時値はブロック終了時に即座に(ブロック内のローカル変数より前に)ドロップされます。 + +このような一時スコープの縮小により、以下のように 2024 でコンパイルが通らなくなる場合があります。 + + +```rust,edition2024,E0716,compile_fail +// 2021 ではコンパイルが通るが 2024 だと通らない +fn main() { + let x = { &String::from("1234") }.len(); +} +``` + + + +このコードだと、2021 では一時値 `String` はブロックの外側と `len()` の呼び出しを超えて生存し、文の末尾でドロップされます。2024 ではブロック末尾で即座にドロップされるため、一時値が借用中にドロップされる旨のコンパイルエラーが出ます。 + + +対処法は、ブロック式をローカル変数に格上げして、一時値が十分長生きするようにすることです。 ```rust,edition2024 fn main() { @@ -79,25 +172,70 @@ fn main() { } ``` + +特にこの例では、特定の状況下で一時値を本来よりも延命させる、[一時値の延命]ルールを活用しています。 +一時値の `String` が、参照 `&` 下にあるために延命され、次の文で `len()` が呼び出せるようになっています。 + + + +[`if let` の一時スコープ]の節では、`if let` 式の一時スコープによる同様の変更について説明しています。 + +[`if let` の一時スコープ]: temporary-if-let-scope.md +[一時スコープのルール]: https://doc.rust-lang.org/reference/destructors.html#temporary-scopes +[一時値の延命]: https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension + + + +## 移行 + + +残念ながら、コードの意味合いを変えずに末尾式中の一時値の生存期間を短くする書き換え方はありません[^RFC3606]。 +[`tail_expr_drop_order`]リントは、末尾式中の一時値が独自の非自明な `Drop` デストラクタをもつことを検出します。 +`cargo fix --edition` を実行すると警告が出ますが、特段の書き換えはなされません。 +プログラマ自身が警告内容を確認し、変更が必要かどうか判断する必要があります。 + +エディション移行を行わずに警告箇所を確認したい場合は、以下のように記述するとリントを有効化できます。 + + + +```rust +// クレートのトップレベルに以下を追加すると手動移行できる +#![warn(tail_expr_drop_order)] +``` + +[^RFC3606]: 詳細は [RFC 3606](https://github.com/rust-lang/rfcs/pull/3606) に説明されています。 + + + +[`tail_expr_drop_order`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#tail-expr-drop-order