HN 日本語サマリー

← 一覧へ戻る
プログラミング

RustのNever Typeの安定化

Stabilizing Rust's Never Type (lwn.net)

57 pointsby cjd81 コメント

要約

Rust言語の「never type」(「!」で表される)が、2年以上の開発を経て安定版としてリリースされました。この型は、決して値を返さない関数や、値が決して発生しない状況を示すために使用されます。never typeは、コンパイラによるコード最適化を可能にし、型推論を簡素化するという実用的・哲学的な利点があります。今回の安定化には、過去のエディションとの互換性を保ちつつ、型推論のフォールバック(fallback)メカニズムの変更が含まれています。

全文翻訳

LWN.netへようこそ 以下の購読者限定コンテンツは、LWN購読者によって提供されました。 数千人の購読者が、Linuxおよびフリーソフトウェアコミュニティからの最高のニュースのためにLWNに依存しています。 この記事を楽しんだなら、LWNを購読することを検討してください。 LWN.netをご覧いただきありがとうございます! Daroc Alden著 2026年9月8日 関数の戻り値の型は、それが生成するデータの種類を示すべきものです。 Rustの「never type」は、感嘆符(「!」)で表され、言語が決して返らない関数や、値が決して発生しないその他の場所を示すために使用する型です。 長らく、never typeはコンパイラ内部で使用されていましたが、不安定な機能と見なされていました。 8月24日、2年以上の作業を経て、Rustコンパイラコントリビューターの「waffle」は、ついにこの型を安定化させることに成功しました。 その実現に時間がかかった一因は、以前のRustエディションに対する小さな破壊的変更を伴ったためです。コンパイラメンテナーは、それが実際のコードにあまり影響を与えないことを確認する必要がありました。 (注:Rustは感嘆符をマクロ呼び出しを示すためにも使用します。構文の構造上、never typeが有効な場所はマクロ呼び出しを置くのに有効な場所ではなく、その逆もまた然りです。) なぜnever typeなのか? Rustにnever typeが存在する理由は2つあります。1つは実用的、もう1つは哲学的です。 実用的な理由は、より効率的なジェネリックコードを可能にすることです。 例えば、文字列からインスタンス化できる型に使用される標準ライブラリのFromStrトレイトを考えてみましょう。 trait FromStr: Sized { type Err; fn from_str(s: &str) -> Result<Self, Self::Err>; } FromStr::from_str()は、変換された結果、またはカスタムエラー型を返します。 例えば、「foo」を整数に変換しようとすると、ParseIntErrorが返されます。 しかし、一部の型は不可能な変換を持ちます。例えば、文字列をByteStringに変換することは常に可能です。 そのFromStrの実装は、Errをnever typeに設定できます。 そうすれば、コンパイラは返されたResultのエラーブランチが存在しないことを知ることができ、それに触れるかチェックする全てのコードを最適化アウトできます。 impl FromStr for ByteString { type Err = !; fn from_str(s: &str) -> Result<Self, !> { ... } // 同じジェネリックインターフェースを維持しつつ、 // fn from_str(s: &str) -> Self { ... } に相当するコードを生成します。 } 哲学的な理由は、正しい型推論に関係します。 Rustでは、if文やwhileループのような構造は式であり、その結果を変数に代入できます。 プログラマが無限ループを記述した場合、コンパイラはその結果の型を推論する必要があります。 これは実際のコードではあまり起こらないはずですが、そのケースを均一に扱うために特別なルールを追加するよりも、それを均一に扱うことができると、型推論を単純化することが判明しました。 特に、never typeはコードを単純化するのに役立つプロパティを持っています。それは、他の全ての型に自動的に変換(coerces)されます。 これは奇妙に聞こえるかもしれませんが、never typeは値を生成しない計算の「結果」を表すため、安全です。 したがって、コードがnever typeの値を持つと主張する場所であればどこでも、コンパイラはそのコードに到達できないことを知っているため、それを無視しても安全です。 これは型システム駆動のデッドコード削除の一形態です。 これらの理由から、Rustプログラマーは言語の安定版でnever typeを使用したいと考えていました。 それを実現するには、特に厄介なコーナーケースを解決する必要がありました。 Never fallback never typeから他の型への変換の実装方法により、コンパイラは、式の具体的な型を単純に推論できない状況に陥ることがあります。 この例を考えてみましょう。これは、匿名関数(「||」を使用、PythonやLISPのラムダのようなもの)を定義し、それが決して返らず、その後、具体的なエラー型(「?」演算子を使用)を期待する方法で呼び出します。 let function_that_never_returns = || { loop {} }; function_that_never_returns()?; この無限ループには「!」の型が与えられ、その後、関数が返すはずだったものに暗黙的に変換されます。 しかし、関数はローカルで定義され、明示的な型が与えられていないため、コンパイラはその型が何であるかを言うのに十分な情報を持っていません。 この問題は、関数に明示的な戻り値の型を与えることで解決できます。 let function_that_never_returns = || -> Foo { loop {} }; しかし、このような注釈は、関数が何も返せない場合にのみ必要となるため、プログラマに架空の型を割り当てることを要求するのは少し無意味です。 そのため、コンパイラは特別なルールを含んでいます。全ての型推論が完了した後も曖昧な型が決定できない場合、それを指定されたフォールバック型と見なします。 2024年版のRustより前は、そのフォールバック型は「()」(単一の値しか持たないユニット型)でした。 2024年版では、フォールバック型が「!」自体に変更され、実質的に暗黙の変換がキャンセルされました。 コンパイラ内部では、never typeは依然として未知の型に変換され、その後フォールバックしますが、プログラマの観点からは、never typeが型の整合性のために必要な場合にのみ暗黙的に変換されるのと同様の動作になります。 この動作の変更は、技術的には破壊的変更でした。 型推論の一部コードが変わる可能性があり、それがコンパイルエラーにつながる可能性があります。 これがRustのエディションシステムの目的です。フロントエンドデザインの破壊的変更を、古いコードを壊したり、エコシステム全体を一度に更新したりすることなく許可することです。 しかし、この場合、新しい動作を古いエディションにバックポートしたい理由がありました。 Never infallible 長年、標準ライブラリにはnever typeの不安定な性質を回避するためにInfallible型がありました。 それはnever typeと同じ意味論的な目的を果たしましたが、特別なコンパイラサポートはありませんでした。 そのため、それを使用したコードは技術的には正しいものの、最適化されない(例えば、列挙型に余分なタグのレイヤーがある、またはデッドコードが出力される)という非効率性がありました。 never typeが最終的に安定化されたときに、Infallibleが「!」の型エイリアスになり、その古いコードがすべて静かに効率化されることが計画されていました。 しかし、Infallibleの再定義が誤って破壊的変更になってしまったいくつかの方法が指摘されました。 「!」には暗黙の変換があるため、Infallibleの定義を変更すると、既存のコードが型チェックのために追加の型指定子を必要とする可能性があります。 幸いなことに、Infallibleの定義の変更とデフォルトのフォールバック型の変更は、両方とも破壊的変更ですが、ほぼ相殺されます。 標準ライブラリのInfallible型を参照するコードは、名前で引き続き機能します。問題となるのは、型推論が暗黙的にInfallibleを生成すると期待される場所であり、既存のコードが壊れるリスクを poses する可能性があります。 Infallibleがnever typeの型エイリアスになると、それらの場所はnever type fallbackを経験する可能性があり、それが推論型を変更し、never fallback型が同時に更新されない場合にコンパイルエラーを引き起こす可能性があります。 両方の変更が同時に行われることで、Rustメンテナーは、既存のRustコードのほとんどすべてがコンパイルされ続けると信じていましたが、「ほとんどすべて」は後方互換性のない変更を扱う際に安心できる修飾子ではありません。 Rustコミュニティには、この問題に対する解決策があります。それは、craterというツールです。craterは、crates.ioから公開されている全てのRustライブラリをダウンロードしてコンパイルし、コンパイラ変更によって壊れるコードを検索します。 Never say never Waffleは4月にcraterを実行し、3,300のクレートが影響を受けたことが判明しました。