diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6d03c91..6a5a095 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -113,7 +113,7 @@ - [Rust 2024](rust-2024/index.md) - [言語](rust-2024/language.md) - - [RPIT lifetime capture rules](rust-2024/rpit-lifetime-capture.md) + - [RPIT におけるライフタイムキャプチャ規則](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) - [Match ergonomics reservations](rust-2024/match-ergonomics.md) diff --git a/src/rust-2024/rpit-lifetime-capture.md b/src/rust-2024/rpit-lifetime-capture.md index a49d889..2768638 100644 --- a/src/rust-2024/rpit-lifetime-capture.md +++ b/src/rust-2024/rpit-lifetime-capture.md @@ -1,23 +1,54 @@ -> **Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、[原文(英語版)](https://doc.rust-lang.org/nightly/edition-guide/introduction.html)をご参照ください。** - + + +# RPIT におけるライフタイムキャプチャ規則 + + +本節では、[RFC 3498] で導入された**ライフタイムキャプチャ規則 2024** に関する変更事項と、([RFC 3617] で導入された)不透明型への**精密なキャプチャ**を使ったコードへの移行方法を説明します。 [RFC 3498]: https://github.com/rust-lang/rfcs/pull/3498 [RFC 3617]: https://github.com/rust-lang/rfcs/pull/3617 + +## 概要 + + + +- Rust 2024 では、`use<..>` を書かない限り、スコープ内の **すべての** ジェネリックパラメータ(ライフタイムパラメータを含む)が暗黙にキャプチャされます。 +- `Captures` パターン(`Captures<..>` 境界)や outlive パターン(`'_` 境界)は、(どのエディションでも)`use<..>` 境界に書き換えたり、(Rust 2024 では)削除したりできます。 + +## 詳細 + + + +### キャプチャ + + +戻り値としての `impl Trait` (return-position impl Trait, 以下 RPIT) 不透明型が「ジェネリックパラメータをキャプチャする」とは、不透明型に隠蔽された型がそのパラメータを使用できる、ということです。 +Rust 1.82 では `use<..>` 境界が導入され、どのジェネリックパラメータがキャプチャされているのかを明示することができるようになりました。 +この構文は Rust 2024 への移行にも活用できますし、本節では各エディションにおける暗黙のキャプチャ規則を説明するためにも用います。 +`use<..>` 境界は次のように使用します。 + + +```rust +# #![feature(precise_capturing)] +fn capture<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { + // ~~~~~~~~~~~~~~~~~~~~~~~ + // これが RPIT 不透明型です。 + // + // `'a` と `T` をキャプチャしています。 + (x, y) + //~~~~~~ + // 型 `(&'a (), T)` が隠蔽されています。 + // + // この型が `'a` と `T` を使えるのは、これらがキャプチャされているからです。 +} +``` + + +どのジェネリックパラメータがキャプチャされるか次第で、不透明型が使える場面も決まります。 +例えば、以下のコードではライフタイム `'a` が使用されていないにもかかわらず、`'a` がキャプチャされているためエラーになります。 ```rust,compile_fail # #![feature(precise_capturing)] @@ -42,10 +94,15 @@ fn capture<'a>(_: &'a ()) -> impl Sized + use<'a> {} fn test<'a>(x: &'a ()) -> impl Sized + 'static { capture(x) //~^ ERROR lifetime may not live long enough + // エラー: ライフタイムが十分に長生きしません } ``` + + +一方で、こうすれば大丈夫です。 ```rust # #![feature(precise_capturing)] @@ -56,12 +113,26 @@ fn test<'a>(x: &'a ()) -> impl Sized + 'static { } ``` + + +### `use<..>` がない場合のエディションごとの挙動の違い + +`use<..>` が明示されていない場合、スコープ内のジェネリックパラメータのうちどれが暗黙にキャプチャされるかは、エディションによって挙動が異なります。 + + + +スコープ内のジェネリックな型パラメータと const パラメータは、`use<..>` が明示されていないときはエディションにかかわらず全て暗黙にキャプチャされます。 +例えば以下の通りです。 + +```rust +# #![feature(precise_capturing)] +fn f_implicit() -> impl Sized {} +// ~~~~~~~~~~ +// `use<..>` が明示されていません。 +// +// どのエディションでも、上記は以下と等価です。 +fn f_explicit() -> impl Sized + use {} +``` + + +ジェネリックなライフタイムパラメータに関しては、Rust 2021 以前のエディションでは、`use<..>` が明示されていない場合、トップレベルの関数か固有 impl [^1] 内の関連関数・メソッドにおける RPIT 不透明型の境界に明示的に記述されているものだけがキャプチャされます。一方、Rust 2024 以降では、スコープ内のジェネリックなライフタイムパラメータは全てキャプチャされます。例えば以下の通りです。 + + + +```rust +# #![feature(precise_capturing)] +fn f_implicit(_: &()) -> impl Sized {} +// Rust 2021 以前では、上記の定義は以下と等価です。 +fn f_2021(_: &()) -> impl Sized + use<> {} +// Rust 2024 以降では、上記の定義は以下と等価です。 +fn f_2024(_: &()) -> impl Sized + use<'_> {} +``` + + +これは、他のシグネチャにおける RPIT 不透明型の挙動に合わせたものです。 +実際、トレイト impl [^2] 内の関連関数・メソッド、トレイト定義内の RPIT(Return-Position Impl Trait In Trait; RPITIT)、`asnyc fn` によって生成される不透明な `Future` 型は、`use<..>` が明示されていない場合、エディションにかかわらず、スコープ内のジェネリックなライフタイムパラメータを全て暗黙にキャプチャするようになっています。 +[^1]: 訳注: 固有 impl とは、`struct MyStruct;` に対する `impl MyStruct { }` ブロックのように、型に対して直接定義される `impl` ブロックのことです。 +[^2]: 訳注: トレイト impl とは、`trait MyTrait` に対する `impl MyTrait for MyStruct { }` ブロックのように、トレイトの実装を与えるための `impl` ブロックのことです。 + + + +### 外側のジェネリックパラメータ + +暗黙にキャプチャされるジェネリックパラメータを決定するとき、外側の impl で定義されたパラメータはスコープ内のパラメータと見なされます。 +例えば以下の通りです。 + + + +```rust +# #![feature(precise_capturing)] +struct S((T, [(); C])); +impl S { +// ~~~~~~~~~~~~~~~~~ +// これらのジェネリックパラメータはスコープ内にあります。 + fn f_implicit() -> impl Sized {} + // ~ ~~~~~~~~~~ + // ^ これもスコープ内です。 + // ^ + // | + // `use<..>` 境界が明示されていません。 + // + // どのエディションでも、これは以下と等価です。 + fn f_explicit() -> impl Sized + use {} +} +``` + + +### 高階束縛のライフタイム + + +同様に、高階の `for<..>` 束縛で定義されたライフタイムパラメータも、スコープ内のパラメータと見なされます。 +例えば以下の通りです。 + + +```rust +# #![feature(precise_capturing)] +trait Tr<'a> { type Ty; } +impl Tr<'_> for () { type Ty = (); } +fn f_implicit() -> impl for<'a> Tr<'a, Ty = impl Copy> {} +// Rust 2021 以前では、上記は以下と等価です。 +fn f_2021() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {} +// Rust 2024 以降では、上記は以下と等価です。 +//fn f_2024() -> impl for<'a> Tr<'a, Ty = impl Copy + use<'a>> {} +// ~~~~~~~~~~~~~~~~~~~~ +// ただし、ネストされた不透明型における高階ライフタイムのキャプチャは +// まだサポート外であることにご注意ください。 +``` + + + +### 引数としての impl Trait (APIT) + +引数としての impl Trait (argument position impl Trait, 以下 APIT) で作られる匿名(無名)ジェネリックパラメータも、スコープ内のパラメータと見なされます。 +例えば以下の通りです。 + + +```rust +# #![feature(precise_capturing)] +fn f_implicit(_: impl Sized) -> impl Sized {} +// ~~~~~~~~~~ +// これが APIT です。 +// +// 上記は「概ね」以下と等価です。 +fn f_explicit<_0: Sized>(_: _0) -> impl Sized + use<_0> {} +``` + + +ただし、これは「完全に」等価ではありません。後者のようにジェネリックパラメータを名前付きにした場合、turbofish `::<>` 構文で引数を明示することができてしまうからです。 +実際には `use<..>` 境界で匿名ジェネリックパラメータを明示したい場合、名前付きジェネリックパラメータとして定義し直すしかありません。 + + + +## 移行 + + +### 移行時にキャプチャしすぎを回避する + +Rust 2024 で新たにキャプチャされるライフタイムは、`impl_trait_overcaptures` リントで検出可能です。 +このリントは `cargo fix --edition` で自動適用される `rust-2024-compatibility` リントの一部です。 +ほとんどの場合、リントが必要に応じて `use<..>` を自動的に挿入し、Rust 2024 への移行時にもキャプチャされるライフタイムが増えないようにします。 + + + +コードを Rust 2024 互換に移行するには、以下を実行します。 ```sh cargo fix --edition ``` + + +例えば、 ```rust fn f<'a>(x: &'a ()) -> impl Sized { *x } ``` + + +は ```rust # #![feature(precise_capturing)] fn f<'a>(x: &'a ()) -> impl Sized + use<> { *x } ``` +に書き換えられます。 + + +`use<>` 境界がないと、Rust 2024 で返り値の不透明型が `'a` を新たにキャプチャしてしまいますが、移行リントが `use<>` を追加したことにより、意味合いが変わらないようになっています。 + + + +## APIT が絡む移行 + +ジェネリックパラメータが名無しであるために `use<..>` で参照できず、リントが自動移行に失敗する場合があります。 +この場合、手動の変更が必要であるとリントが報告します。 +たとえば、以下のコードを考えます。 + + + +```rust,edition2021 +fn f<'a>(x: &'a (), y: impl Sized) -> impl Sized { (*x, y) } +// ^^ ~~~~~~~~~~ +// ここで APIT が使われています。 +// +//~^ WARN `impl Sized` will capture more lifetimes than possibly intended in edition 2024 +//~| NOTE specifically, this lifetime is in scope but not mentioned in the type's bounds +// (訳) +//~^ 警告: 2024 エディションにすると、`impl Sized` が想定より多くのライフタイムをキャプチャする可能性があります。 +//~| 情報: 特に、このライフタイムはスコープ内にありますが、型境界に明示れていません +# +# fn test<'a>(x: &'a (), y: ()) -> impl Sized + 'static { +# f(x, y) +# } +``` + +ここで、変換が失敗するのは、ジェネリックパラメータを全て `use<..>` 境界で「名指し」したいにもかかわらず、無名型を用いた APIT が使われているからです。 +Rust 2024 になってもライフタイムがキャプチャされないようにするには、例えば以下のように、型引数を名前付きに変更する必要があります。 + +```rust +# #![feature(precise_capturing)] +# #![deny(impl_trait_overcaptures)] +fn f<'a, T: Sized>(x: &'a (), y: T) -> impl Sized + use { (*x, y) } +// ~~~~~~~~ +// 型引数に名前がつきました。 +# +# fn test<'a>(x: &'a (), y: ()) -> impl Sized + use<> { +# f(x, y) +# } +``` + + + +なお、これは API の若干の変更にあたります。turbofish `::<>` 構文で型引数が明示的に指定できるようになったからです。 +これを避けたい場合は、引き続き `use<..>` を明示しないことでライフタイムがキャプチャされるようになっても良いか判断する必要があります。 +特に、隠蔽された型が将来的にライフタイムをキャプチャしうる余地を残したい場合は、そうした方がよいでしょう。 + + +### `Captures` パターンをやめる + +Rust 1.82 で `use<..>` 境界を用いた精密なキャプチャが使えるようになるまで、RPIT 不透明型がライフタイムを正しくキャプチャするためによく使われていたのが `Captures` パターンです。 +例えば以下の通りです。 + + + +```rust +#[doc(hidden)] +pub trait Captures {} +impl Captures for U {} + +fn f<'a, T>(x: &'a (), y: T) -> impl Sized + Captures<(&'a (), T)> { +// ~~~~~~~~~~~~~~~~~~~~~ +// これがいわゆる Captures パターンです。 + (x, y) +} +# +# fn test<'t, 'x>(t: &'t (), x: &'x ()) { +# f(t, x); +# } +``` + + +`use<..>` 境界構文が導入された今、`Captures` パターンは不要となり、どのエディションでも以下のように書き換えることができるようになりました。 ```rust # #![feature(precise_capturing)] @@ -235,7 +536,11 @@ fn f<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> { # } ``` + + +Rust 2024 では `use<..>` はそもそも省略可能な場合が多いです。上記は以下のように書き換えられます。 ```rust,edition2024 # #![feature(lifetime_capture_rules_2024)] @@ -248,12 +553,26 @@ fn f<'a, T>(x: &'a (), y: T) -> impl Sized { # } ``` + + +このような移行は自動ではなされず、また Rust 2024 でも `Captures` パターンは使用可能ですが、古い書き方からは脱却したほうがよいでしょう。 + +### outlive パターンをやめる + + + +Rust 1.82 で `use<..>` 境界を用いた精密なキャプチャが使えるようになるまで、不透明型に隠蔽された型がライフタイムを要求する場合に outlive パターンが広く使われていました。 +例えば以下の通りです。 + + +```rust +fn f<'a, T: 'a>(x: &'a (), y: T) -> impl Sized + 'a { + // ~~~~ ~~~~ + // ^ ここで outlive パターンを使っています。 + // | + // このパターンのためだけに、追加の境界が必要になっています。 + (x, y) +// ~~~~~~ +// 型 `(&'a (), T)` が隠蔽されています。 +} +``` + +このパターンは `Captures` パターンほど古の産物ではないですが、`Captures` パターンほど正しくもありませんでした。 +上の例のように、`T` を構成するライフタイムは `'a` と無関係であるのに、パターンを適用するために `T: 'a` を指定せざるを得ないからです。 +関数を使う側としては、思いがけない不当な制約となってしまいます。 + + + +精密なキャプチャを使えば、上記のコードはどのエディションでも以下のように書き換えられます。 ```rust # #![feature(precise_capturing)] @@ -281,7 +623,11 @@ fn f(x: &(), y: T) -> impl Sized + use<'_, T> { # } ``` + + +Rust 2024 では、 `use<..>` 境界は多くの場合で明示が不要なので、次のように書くこともできます。 ```rust,edition2024 # #![feature(precise_capturing)] @@ -295,4 +641,8 @@ fn f(x: &(), y: T) -> impl Sized { # } ``` + + +このような移行は自動ではなされず、また Rust 2024 でも outlive パターンは使用可能ですが、古い書き方からは脱却したほうがよいでしょう。