From 18d5952e60627dd588450a84504f4eecf6f8acdd Mon Sep 17 00:00:00 2001 From: TonalidadeHidrica <47710717+TonalidadeHidrica@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:45:35 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E3=80=8C=E3=82=AF=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=A3=E3=81=AF=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=89=E3=81=94=E3=81=A8=E3=81=AB=E3=82=AD=E3=83=A3=E3=83=97?= =?UTF-8?q?=E3=83=81=E3=83=A3=E3=81=99=E3=82=8B=E3=80=8D=E3=82=92=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TranslationTable.md | 5 +- src/rust-2021/disjoint-capture-in-closures.md | 247 ++++++++++++++++++ 2 files changed, 251 insertions(+), 1 deletion(-) diff --git a/TranslationTable.md b/TranslationTable.md index 28d6f03..dc97f8d 100644 --- a/TranslationTable.md +++ b/TranslationTable.md @@ -70,6 +70,7 @@ | diverging | 発散する〜(上の diverge を修飾語として使った場合) | documentation comment | ドキュメンテーションコメント | documentation test | ドキュメンテーションテスト +| drop | ドロップ | early return | 早期リターン | edition | エディション | empty tuple | 空タプル @@ -130,10 +131,12 @@ | memory | メモリ | method | メソッド | monomorphization | 単相化 -| move | ムーブ +| move | ムーブ(する) +| move out | ムーブする、ムーブアウトする | mutability | ミュータビリティ | mutable | ミュータブル | mutable binding | ミュータブルな束縛 +| mutate | 変更する、書き換える | mutual-exclusion | 相互排他 | null | ヌル | object-safe | オブジェクト安全 diff --git a/src/rust-2021/disjoint-capture-in-closures.md b/src/rust-2021/disjoint-capture-in-closures.md index 4b1d832..c268b18 100644 --- a/src/rust-2021/disjoint-capture-in-closures.md +++ b/src/rust-2021/disjoint-capture-in-closures.md @@ -1,21 +1,54 @@ + +# クロージャはフィールドごとにキャプチャする + + + + + + +## 概要 + +- `|| a.x + 1` が `a` でなく `a.x` だけをキャプチャするようになりました。 +- これにより、ドロップのタイミングが変わったり、クロージャが `Send` や `Clone` を実装するかどうかが変わったりします。 + - `cargo fix` は、可能だと判断したときに、 `let _ = &a` のような文を挿入して、クロージャが変数全体をキャプチャするように強制します。 + + + +## 詳細 + +[クロージャ](https://doc.rust-lang.org/book/ch13-01-closures.html)は、本体の中で使用しているすべてのものを自動的にキャプチャします。 +例えば、`|| a + 1` と書くと、周囲のコンテキスト中の `a` への参照が自動的にキャプチャされます。 + + + +Rust 2018 以前では、クロージャに使われているのが1つのフィールドだけであっても、クロージャは変数全体をキャプチャします。 +例えば、 `|| a.x + 1` は `a.x` への参照だけでなく、`a` への参照をキャプチャします。 +`a` 全体がキャプチャされると、`a` の他のフィールドの値を書き換えたりムーブしたりできなくなります。従って以下のようなコードはコンパイルに失敗します: + +```rust,ignore +let a = SomeStruct::new(); +drop(a.x); // 構造体のフィールドの1つをムーブする +println!("{}", a.y); // OK: 構造体の他のフィールドは、まだ使える +let c = || println!("{}", a.y); // エラー: `a` 全体をキャプチャしようとする +c(); +``` + + + +Rust 2021 からは、クロージャのキャプチャはより精密になります。 特に、使用されるフィールドだけがキャプチャされるようになります +(場合によっては、使用する変数以外にもキャプチャすることもあり得ます。詳細に関しては Rust リファレンスを参照してください)。 +したがって、上記のコードは Rust 2021 では問題ありません。 + + +フィールドごとのキャプチャは [RFC 2229](https://github.com/rust-lang/rfcs/blob/master/text/2229-capture-disjoint-fields.md) の一部として提案されました。この RFC にはより詳しい動機が記載されています。 + +## 移行 + + + +Rust 2018 から Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリント`rust_2021_incompatible_closure_captures` が追加されています。 + + +`rustfix` でコードを Rust 2021 エディションに適合させるためには、次のように実行します。 ```sh cargo fix --edition ``` + + +以下では、クロージャによるキャプチャが出現するコードについて、自動移行が失敗した場合に手動で Rust 2021 に適合するように移行するにはどうすればいいかを考察します。 +移行がどのようになされるか知りたい人も以下をお読みください。 + +クロージャによってキャプチャされる変数が変わると、プログラムの挙動が変わったりコンパイルできなくなったりすることがありますが、その原因は以下の2つです: + + + +- ドロップの順序や、デストラクタが走るタイミングが変わる場合([詳細](#drop-order)) +- クロージャが実装するトレイトが変わる場合([詳細](#drop-order)) + + +以下のような状況を検知すると、`cargo fix` は "ダミーの let" をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします: + +```rust +let x = (vec![22], vec![23]); +let c = move || { + // `x` 全体が強制的にキャプチャされるための "ダミーの let" + let _ = &x; + + // それがないと、`x.0` だけがここでキャプチャされる + println!("{:?}", x.0); +}; +``` + + + +この解析は保守的です。ほとんどの場合、ダミーの let は問題なく消すことができ、消してもプログラムはきちんと動きます。 + +### ワイルドカードパターン + + + +クロージャは本当に読む必要のあるデータだけをキャプチャするようになったので、次のコードは `x` をキャプチャしません: + +```rust +let x = 10; +let c = || { + let _ = x; // 何もしない +}; + +let c = || match x { + _ => println!("Hello World!") +}; +``` + + +`_` パターンは右辺を無視するので、この `let _ = x` は何もせず、`x` はメモリ上のある場所(この場合は変数)を指し示します。 + + + +この変更(いくつかの値がキャプチャされなくなること)そのものによってコード変更の提案がなされることはありませんが、後で説明する「ドロップ順序」の変更と組み合わせると、提案がなされる場合もあります。 + + +**ちなみに:** 似たような式の中には、同じく自動挿入される "ダミーの let" であっても、`let _ = &x` のように「何もしない」わけではない文もあります。なぜかというと、右辺(`&x`)はメモリ上のある場所を指し示すのではなく、値が評価されるべき式となるからです(その評価結果は捨てられますが)。 + +### ドロップの順序 + + + +クロージャが変数 `t` の所有権を取るとき、`t` がドロップされるのは `t` がスコープ外に出たときではなく、そのクロージャがドロップされたときになります: + +```rust +# fn move_value(_: T){} +{ + let t = (vec![0], vec![0]); + + { + let c = || move_value(t); // t はここでムーブされる + } // c がドロップされ、そのときにタプル `t` もまたドロップされる +} // t はここでスコープを抜ける +``` + + +上記のコードの挙動は Rust 2018 と Rust 2021 で同じです。ところが、クロージャが変数の_一部_の所有権を取るとき、違いが発生します: + + +```rust +# fn move_value(_: T){} +{ + let t = (vec![0], vec![0]); + + { + let c = || { + // Rust 2018 では、`t` 全体がキャプチャされる。 + // Rust 2018 では、`t.0` だけがキャプチャされる + move_value(t.0); + }; + + // Rust 2018 では、 `c` (と `t`) の両方が + // このブロックを抜けるときにドロップされる。 + // + // Rust 2021 では、 `c` と `t.0` の両方が + // このブロックを抜けるときにドロップされる。 + } + +// Rust 2018 では、`t` はすでにムーブされており、ここではドロップされない +// +// Rust 2021 では、`t.0` はムーブされているが、 +// `t.1` は残っており、ここでドロップされる +} +``` + + +ほとんどの場合、ドロップのタイミングが変わってもメモリが解放されるタイミングが変わるだけで、さほど問題にはなりません。 +しかし、`Drop` の実装に副作用のある(いわゆるデストラクタである)場合、ドロップの順序が変わるとプログラムの意味も変わってしまいます。 +その場合は、コンパイラはダミーの `let` を挿入して変数全体がキャプチャされるように提案します。 + + + +### トレイト実装 + + +何がキャプチャされているかによって、クロージャには自動的に以下のトレイトが実装されます: + +- [`Clone`]: キャプチャされた値がすべて [`Clone`] を実装していた場合。 +- [`Send`], [`Sync`], [`UnwindSafe`] などの[自動トレイト]: キャプチャされた値がすべてそのトレイトを実装していた場合。 + + +[自動トレイト]: https://doc.rust-lang.org/nightly/reference/special-types-and-traits.html#auto-traits +[`clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html +[`send`]: https://doc.rust-lang.org/std/marker/trait.Send.html +[`sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html +[`unwindsafe`]: https://doc.rust-lang.org/std/marker/trait.UnwindSafe.html + + + +Rust 2021 では、キャプチャされる値が変わることによって、クロージャが実装するトレイトも変わることがあります。 +先ほどの移行リントは、それぞれのクロージャについて、これまで実装されていた自動トレイトが何であるか、そして移行後もそれらが残るかどうかを調べます。 +もし今まで実装されていたトレイトが実装されなくなる場合、「ダミーの let」が挿入されます。 + +例えば、スレッド間で生ポインタを受け渡しする一般的な方法に、ポインタを構造体でラップし、そのラッパー構造体に自動トレイト `Send`/`Sync` を実装するというものがあります。 +`thread::spawn` に渡されるクロージャが使うのは、ラッパー構造体のうち特定の変数だけですが、キャプチャされるのはラッパー構造体全体です。 +ラッパー構造体は `Send`/`Sync` なので、コードは安全であるとみなされ、コンパイルは成功します。 + + + +フィールドごとのキャプチャが導入されると、キャプチャ内で使用されているフィールドだけがキャプチャされますが、フィールドの中身はもともと `Send`/`Sync` でなかったのですから、せっかくラッパーを作っても元の木阿弥です。 + + +```rust +use std::thread; + +struct Ptr(*mut i32); +unsafe impl Send for Ptr {} + + +let mut x = 5; +let px = Ptr(&mut x as *mut i32); + +let c = thread::spawn(move || { + unsafe { + *(px.0) += 10; + } +}); // クロージャは px.0 をキャプチャしたが、これは Send ではない +``` From 14a15c22ad314bdc186e4c0eac57179c3634d8f2 Mon Sep 17 00:00:00 2001 From: TonalidadeHidrica <47710717+TonalidadeHidrica@users.noreply.github.com> Date: Thu, 4 Nov 2021 19:50:34 +0900 Subject: [PATCH 2/8] =?UTF-8?q?mdbook-transcheck=20=E3=81=AB=E9=80=9A?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/rust-2021/disjoint-capture-in-closures.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/rust-2021/disjoint-capture-in-closures.md b/src/rust-2021/disjoint-capture-in-closures.md index c268b18..205a959 100644 --- a/src/rust-2021/disjoint-capture-in-closures.md +++ b/src/rust-2021/disjoint-capture-in-closures.md @@ -2,10 +2,7 @@ # Disjoint capture in closures --> -# クロージャはフィールドごとにキャプチャする - - - +# クロージャはフィールドごとにキャプチャする -- ドロップの順序や、デストラクタが走るタイミングが変わる場合([詳細](#drop-order)) -- クロージャが実装するトレイトが変わる場合([詳細](#drop-order)) +- ドロップの順序や、デストラクタが走るタイミングが変わる場合([詳細](#ドロップの順序)) +- クロージャが実装するトレイトが変わる場合([詳細](#トレイト実装)) - -```rust,ignore -let a = SomeStruct::new(); -drop(a.x); // 構造体のフィールドの1つをムーブする -println!("{}", a.y); // OK: 構造体の他のフィールドは、まだ使える -let c = || println!("{}", a.y); // エラー: `a` 全体をキャプチャしようとする + // エラー: `a` 全体をキャプチャしようとする c(); ``` @@ -127,25 +120,14 @@ Whenever any of the scenarios below are detected, `cargo fix` will insert a "dum 以下のような状況を検知すると、`cargo fix` は "ダミーの let" をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします: - - -```rust -let x = (vec![22], vec![23]); -let c = move || { // `x` 全体が強制的にキャプチャされるための "ダミーの let" let _ = &x; + // Otherwise, only `x.0` would be captured here // それがないと、`x.0` だけがここでキャプチャされる println!("{:?}", x.0); }; @@ -169,23 +151,11 @@ Closures now only capture data that needs to be read, which means the following クロージャは本当に読む必要のあるデータだけをキャプチャするようになったので、次のコードは `x` をキャプチャしません: - - -```rust -let x = 10; -let c = || { - let _ = x; // 何もしない + // 何もしない }; let c = || match x { @@ -223,7 +193,6 @@ When a closure takes ownership of a value from a variable `t`, that value is the クロージャが変数 `t` の所有権を取るとき、`t` がドロップされるのは `t` がスコープ外に出たときではなく、そのクロージャがドロップされたときになります: - - -```rust -# fn move_value(_: T){} -{ - let t = (vec![0], vec![0]); - - { - let c = || move_value(t); // t はここでムーブされる - } // c がドロップされ、そのときにタプル `t` もまたドロップされる -} // t はここでスコープを抜ける + // t はここでスコープを抜ける ``` _一部_の所有権を取るとき、違いが発生します: - - -```rust -# fn move_value(_: T){} -{ - let t = (vec![0], vec![0]); - - { - let c = || { - // Rust 2018 では、`t` 全体がキャプチャされる。 - // Rust 2018 では、`t.0` だけがキャプチャされる - move_value(t.0); - }; - - // Rust 2018 では、 `c` (と `t`) の両方が - // このブロックを抜けるときにドロップされる。 - // - // Rust 2021 では、 `c` と `t.0` の両方が - // このブロックを抜けるときにドロップされる。 - } - -// Rust 2018 では、`t` はすでにムーブされており、ここではドロップされない -// // Rust 2021 では、`t.0` はムーブされているが、 // `t.1` は残っており、ここでドロップされる } @@ -372,7 +312,6 @@ With disjoint captures, only the specific field mentioned in the closure gets ca フィールドごとのキャプチャが導入されると、キャプチャ内で使用されているフィールドだけがキャプチャされますが、フィールドの中身はもともと `Send`/`Sync` でなかったのですから、せっかくラッパーを作っても元の木阿弥です。 - - -```rust -use std::thread; - -struct Ptr(*mut i32); -unsafe impl Send for Ptr {} - - -let mut x = 5; -let px = Ptr(&mut x as *mut i32); - -let c = thread::spawn(move || { - unsafe { - *(px.0) += 10; - } -}); // クロージャは px.0 をキャプチャしたが、これは Send ではない + // クロージャは px.0 をキャプチャしたが、これは Send ではない ``` From c5393d8717c870c23b4a71afbe4c9d1f7764be66 Mon Sep 17 00:00:00 2001 From: TonalidadeHidrica <47710717+TonalidadeHidrica@users.noreply.github.com> Date: Fri, 12 Nov 2021 22:02:47 +0900 Subject: [PATCH 6/8] Apply suggestions from code review Co-authored-by: Tatsuya Kawano --- src/rust-2021/disjoint-capture-in-closures.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/rust-2021/disjoint-capture-in-closures.md b/src/rust-2021/disjoint-capture-in-closures.md index e283aa8..84440ef 100644 --- a/src/rust-2021/disjoint-capture-in-closures.md +++ b/src/rust-2021/disjoint-capture-in-closures.md @@ -32,7 +32,7 @@ automatically capture anything that you refer to from within their body. For example, `|| a + 1` automatically captures a reference to `a` from the surrounding context. --> -[クロージャ](https://doc.rust-lang.org/book/ch13-01-closures.html)は、本体の中で使用しているすべてのものを自動的にキャプチャします。 +[クロージャ](https://doc.rust-jp.rs/book-ja/ch13-01-closures.html)は、本体の中で使用しているすべてのものを自動的にキャプチャします。 例えば、`|| a + 1` と書くと、周囲のコンテキスト中の `a` への参照が自動的にキャプチャされます。 -Rust 2018 から Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリント`rust_2021_incompatible_closure_captures` が追加されています。 +Rust 2018 のコードベースから Rust 2021 への自動移行の支援のため、2021 エディションには、移行用のリント`rust_2021_incompatible_closure_captures` が追加されています。 -`_` パターンは右辺を無視するので、この `let _ = x` は何もせず、`x` はメモリ上のある場所(この場合は変数)を指し示します。 +この `let _ = x` は何もしません。 +なぜなら、`_` パターンは右辺を無視し、さらに、`x` はメモリ上のある場所(この場合は変数)への参照だからです。 -クロージャが変数 `t` の所有権を取るとき、`t` がドロップされるのは `t` がスコープ外に出たときではなく、そのクロージャがドロップされたときになります: +クロージャが変数 `t` の値の所有権を取るとき、その値がドロップされるのは `t` がスコープ外に出たときではなく、そのクロージャがドロップされたときになります: ```rust # fn move_value(_: T){} @@ -253,7 +254,7 @@ In most cases, dropping values at different times just affects when memory is fr --> ほとんどの場合、ドロップのタイミングが変わってもメモリが解放されるタイミングが変わるだけで、さほど問題にはなりません。 -しかし、`Drop` の実装に副作用のある(いわゆるデストラクタである)場合、ドロップの順序が変わるとプログラムの意味も変わってしまいます。 +しかし、`Drop` の実装に副作用のある(いわゆるデストラクタである)場合、ドロップの順序が変わるとプログラムの意味が変わってしまうかもしれません。 その場合は、コンパイラはダミーの `let` を挿入して変数全体がキャプチャされるように提案します。 -# クロージャはフィールドごとにキャプチャする +# クロージャはフィールドごとにキャプチャする -以下のような状況を検知すると、`cargo fix` は "ダミーの let" をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします: +以下のような状況を検知すると、`cargo fix` は「ダミーの let」をクロージャの中に挿入して、強制的に全ての変数がキャプチャされるようにします: ```rust let x = (vec![22], vec![23]); let c = move || { // "Dummy let" that forces `x` to be captured in its entirety - // `x` 全体が強制的にキャプチャされるための "ダミーの let" + // `x` 全体が強制的にキャプチャされるための「ダミーの let」 let _ = &x; // Otherwise, only `x.0` would be captured here @@ -289,7 +289,7 @@ Closures automatically implement the following traits based on what values they [`clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html [`send`]: https://doc.rust-lang.org/std/marker/trait.Send.html [`sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html -[`unwindsafe`]: https://doc.rust-lang.org/std/marker/trait.UnwindSafe.html +[`unwindsafe`]: https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html