diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 6d03c91..38d0fca 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -121,7 +121,7 @@ - [Unsafe attributes](rust-2024/unsafe-attributes.md) - [`unsafe_op_in_unsafe_fn` warning](rust-2024/unsafe-op-in-unsafe-fn.md) - [Disallow references to `static mut`](rust-2024/static-mut-references.md) - - [Never type fallback change](rust-2024/never-type-fallback.md) + - [never 型のフォールバック先の変更](rust-2024/never-type-fallback.md) - [Macro fragment specifiers](rust-2024/macro-fragment-specifiers.md) - [Missing macro fragment specifiers](rust-2024/missing-macro-fragment-specifiers.md) - [`gen` keyword](rust-2024/gen-keyword.md) diff --git a/src/rust-2024/never-type-fallback.md b/src/rust-2024/never-type-fallback.md index c600f58..2f564f5 100644 --- a/src/rust-2024/never-type-fallback.md +++ b/src/rust-2024/never-type-fallback.md @@ -1,18 +1,42 @@ -> **Rust Edition Guide は現在 Rust 2024 のアップデート作業に向けて翻訳作業中です。本ページはある時点での英語版をコピーしていますが、一部のリンクが動作しない場合や、最新情報が更新されていない場合があります。問題が発生した場合は、[原文(英語版)](https://doc.rust-lang.org/nightly/edition-guide/introduction.html)をご参照ください。** - + + +# never 型のフォールバック先の変更 + + +## 概要 + +- never 型(`!`)から任意型への(never-to-any の)型強制において、失敗時のフォールバック先が never 型(`!`)でなくユニット型(`()`)となります。 +- [`never_type_fallback_flowing_into_unsafe`] のリントレベルはデフォルトで `deny` (必ずエラー)となりました。 + + + +[`never_type_fallback_flowing_into_unsafe`]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#never-type-fallback-flowing-into-unsafe + +## 詳細 + + + +[型強制サイト][](型強制可能な場所)に `!` (never) と呼ばれる値があるとき、型検査機が任意の型を推論できるように、コンパイラはそこにある種の変換をさし挟みます。 + +```rust,should_panic +# #![feature(never_type)] +// 以下のコードは +let x: u8 = panic!(); + +// コンパイラによって(実質的に)以下のように書き換えられる +let x: u8 = yaba(panic!()); + +// ここで、`yaba` 関数は以下のような関数 +//(`!` は到達不能なコードに対応するので、これは正当なコード) +fn yaba(x: !) -> T { x } +``` + + + +このままだと型推論に失敗したときにコンパイルエラーが発生してしまいます。 + + +```rust,compile_fail,E0282 +# #![feature(never_type)] +# fn yaba(x: !) -> T { x } +// これが +{ panic!() }; +// これになる +{ yaba(panic!()) }; //~ ERROR can't infer the type of `yaba` + //~ (訳) エラー: `yaba` の型が推論できません +``` + + + +このようなエラーを避けるため、コンパイラには、「`yaba` が挿入された場所で型推論に失敗した場合には、代わりにそれをこの型と見做して処理を続ける」というような「失敗時のデフォルト」用の型(フォールバック先の型)が決まっています。 + + +```rust,should_panic +# #![feature(never_type)] +# fn absurd(x: !) -> T { x } +type Fallback = /* フォールバック先をどの型にするかはコンパイラの自由!*/ !; +{ absurd::(panic!()) } +``` + +この現象は、never 型のフォールバックと呼ばれます。 + + + +かつてはフォールバック先の型は `()` 型(ユニット型)でした。 +これにより、フォールバックが発生しなければコンパイラが `()` を推論としないはずの場面であっても、実際には `!` が勝手に `()` に強制されてしまいました。 +この動作は非直感的で、`!` 型の安定化も妨げていました。 + + +2024 エディションから、フォールバック先の型が `!` 型になりました。 +(将来的には全エディションで採用される予定です。) +これによりコンパイラの動作がより直感的になります。 +これからは、別の型に強制される特段の理由がない限り、`!` は `!` のままです。 + +`()` へのフォールバックを前提としたコードは、本変更によってコンパイルエラーになったり、挙動が変わったりするおそれがあります。 + + + +[型強制サイト]: https://doc.rust-lang.org/reference/type-coercions.html#coercion-sites ### `never_type_fallback_flowing_into_unsafe` + +2024 エディションでは、[`never_type_fallback_flowing_into_unsafe`] のデフォルトレベルが `warn`(警告)から `deny`(必ずエラー)に格上げされました。 +このリントは、`unsafe` 文脈における `!` へのフォールバックが関わる動作のうち、未定義動作を引き起こす可能性のあるものを検知します。 +詳細説明はリンク先をご参照ください。 + + + +## 移行 + +自動修正はできませんが、エディションの更新によって壊れうるコードを自動検知することはできます。 +また、過去のエディションにおいてはコードが壊れうる場合に警告が出ます。 + + + +修正方法は、フォールバックが起こらないように強制先となるべき型を明示することです。 +残念ながら、どの型を指定すべきかは自明ではありません。 + + +壊れうるコードの中では比較的よく見られるパターンとしては、`f()?;` という式において、特に `f` の戻り値の型が `Ok` 側の型引数に対してジェネリックな場合が挙げられます。 ```rust # #![allow(dependency_on_unit_never_type_fallback)] @@ -81,10 +198,19 @@ f()?; # } ``` + + +一見、型 `T` は推論不可能に見えますが、`?` 構文の脱糖 (desugaring) 方法の関係で、かつては `()` と、今は `!` と推論されます。 + + +対策としては、`T` の型を明示するとよいです。 + + +```rust,edition2024 +# #![deny(dependency_on_unit_never_type_fallback)] +# fn outer(x: T) -> Result { +# fn f() -> Result { +# Ok(T::default()) +# } +f::<()>()?; +// または +() = f()?; +# Ok(x) +# } +``` + + +他のよくある例としては、クロージャ中でパニックする場合が挙げられます。 ```rust,should_panic # #![allow(dependency_on_unit_never_type_fallback)] @@ -112,7 +256,13 @@ fn run(f: impl FnOnce() -> R) { run(|| panic!()); ``` + + +かつては、`panic!` の返す `!` は `Unit` を実装する `()` に強制されていました。 +本エディションからは、`!` が `!` のままなので、`!` が `Unit` を実装しない以上コンパイルに失敗してしまいます。 +このエラーは、クロージャの返り値の型を明示すると解決できます。 ```rust,edition2024,should_panic # #![deny(dependency_on_unit_never_type_fallback)] @@ -125,7 +275,11 @@ Previously `!` from the `panic!` coerced to `()` which implements `Unit`. Howev run(|| -> () { panic!() }); ``` + + +`f()?` と似た例として、条件分岐等の一方が `!` 型の式で、もう片方が返り値の型が指定されていない関数の呼び出しである場合が挙げられます。 ```rust # #![allow(dependency_on_unit_never_type_fallback)] @@ -136,10 +290,20 @@ if true { }; ``` + +かつては `return` の型である `!` が誤って `()` に強制されていたため、`Default::default()` の戻り値型として `()` が推論されていました。 +本エディションからは `!` が `!` のままと推論されるようになり、`!` は `Default` を実装しないのでコンパイルエラーになります。 + + + +これも同じく、型を明示することで解決できます。 + + +```rust,edition2024 +# #![deny(dependency_on_unit_never_type_fallback)] +() = if true { + Default::default() +} else { + return +}; + +// または + +if true { + <() as Default>::default() +} else { + return +}; +```