Rustの「Never型(!)」がついに安定化 — 10年越しの型システム進化を解説

約19分で読めます by ぽんたぬき
Rustの「Never型(!)」がついに安定化 — 10年越しの型システム進化を解説

Rustの「Never型(!)」がついに安定化 — 10年越しの型システム進化を解説

はじめに:loop {} の「型」を、あなたは説明できますか?

fn server_loop() -> ! {
    loop {
        handle_request();
    }
}

上記の関数の戻り値型 ! を見て、すぐに意味を説明できるRustエンジニアは、おそらく中級者以上でしょう。「絶対に値を返さない関数の型」と答えられれば合格です。では、これはなぜ型として成立するのでしょうか?そして、なぜこの型の完全な安定化に10年もかかったのでしょうか?

2026年8月24日、Rust開発者WaffleLapkin(waffle)によるPR #155499がマージされ、Rust 1.100においてNever型(!)がついに正式安定化を果たしました。2年以上に及ぶ安定化作業と、7,500超のクレートを巻き込んだ互換性検証の末に実現したこのマイルストーンが、Rustの型システムに何をもたらすのか。本記事で詳しく解説します。

本記事で分かること

  • Never型(!)が何を解決する型なのか、基礎から理解する
  • 安定化までに10年かかった技術的・設計的な理由
  • 自分のコードに影響があるか、どう対処すればよいか

Never型(!)とは何か — 基礎から理解する

値を返さない式の型

Rustには「実行が戻ってこない式」が複数存在します。

panic!("致命的なエラー");   // プロセスを終了させる
loop {}                      // 永遠に繰り返す
return;                      // 関数から即座に抜ける
std::process::exit(1);       // プロセスを終了させる

これらの式には共通の性質があります。「その先のコードは絶対に実行されない」ということです。RustはこれをNever型(! という専用の型で表現します。

空型(Empty Type)という考え方

Never型は**空型(Empty Type)**とも呼ばれます。「値がひとつも存在しない型」という意味です。Rustで手動で同等のものを定義しようとすれば、こうなります。

enum Void {}  // バリアントが存在しない enum

Void 型の値を生成することは不可能です。バリアントが一つも無いため、Void::XXX のような書き方ができないからです。! はまさにこの性質を言語レベルで表しています。

なぜ ! はどんな型にもなれるのか

Never型の最も重要な性質は、あらゆる型に暗黙的にキャストできることです。

let x: i32 = if condition {
    42
} else {
    panic!("ここには到達しない")
};

if 式の両辺の型を揃えるため、panic!() の型である !i32 として扱われます。これは「到達不能なコードは安全にどんな型にも変換できる」という直感に基づいています。実際にその値が生成されることがないので、型安全性は維持されます。

()(ユニット型)との違い

よく混同されるのが ()(ユニット型) との違いです。

値の個数 意味
() 1個(空のタプル) 「意味のある値がない」ことを表す
! 0個(到達不能) 「値を返すことが絶対にない」ことを表す

() を返す関数は「処理は完了するが、返すべき値がない」という意味です。一方、! を返す関数は「そもそも処理が完了することがない」ことを型レベルで保証します。


10年越しの安定化 — なぜここまで時間がかかったのか

過去5回の試みと、そのたびの差し戻し

! の構文自体は長年にわたってRustに存在していました。関数の戻り値型として fn foo() -> ! という書き方は、Nightlyビルドで利用されてきました。しかし「型パラメータとして使える完全な型」としての安定化は、5回にわたって試みられ、そのたびに問題が発見されて見送られてきました。

最大の技術的障壁は、後述する型推論フォールバックの変更です。この変更はRustの型推論エンジンの根幹に関わるものであり、既存コードへの影響を最小化しながら正しく実装するために、膨大な設計議論と実装作業が必要でした。

2年以上を要した最終フェーズ

最終的な安定化作業だけで2年以上を要しました。主な理由は、変更の影響範囲の広さです。Rustのエコシステムは現在、crates.ioに数十万のクレートが公開されています。これらへの影響を Crater(後述)で全件検証し、破壊的変更を最小化するためのバックポート対応を重ねた結果、ようやく2026年8月のマージに至りました。

チームが「10年かける価値があった」と判断した理由

Rustチームが長期にわたって粘り強く取り組んだのは、Never型の安定化が単なる機能追加ではなく、型システムの設計上の「穴」を埋める作業だったからです。Infallible という代替品が存在し続けることは、言語設計の一貫性を損なっていました。型システムを完結させることへの強い意志が、10年越しのコミットメントを支えました。


最大の難所:型推論フォールバックの変更

フォールバックとは何か

Rustのコンパイラは、型が一意に決まらないとき「デフォルトの型」として処理する仕組みを持っています。これを型推論フォールバックと呼びます。

let x = [];  // 要素の型が不明 → フォールバックが必要

この仕組みがNever型の安定化に深く関わっています。

旧来(Rust 2021以前):() へフォールバック

以下のようなコードを考えます。

let f = || { loop {} };
// f の戻り値型は何か? → コンパイラが決める

旧来の型推論では、Never型が確定できないケースで () にフォールバックしていました。

// 旧来の動作:!が型パラメータとして未確定 → ()にフォールバック
fn always_fail<E>() -> Result<(), E> {
    Err(todo!())
}

新方式(Rust 2024 edition以降):! へフォールバック

Rust 2024 editionでは、Never型が含まれる式の推論フォールバック先が ! に変更されます。これは設計上より正確な挙動です。「絶対に値を返さない」のだから、! にフォールバックするのが論理的です。

この変更が互換性破壊になるケース

一部のコードでは、フォールバック先が変わることによって型エラーや予期しない挙動が生じます。

// 問題が起きうるパターン:トレイト実装の選択が変わる
trait MyTrait {}
impl MyTrait for () {}
// impl MyTrait for ! {} が定義されていない場合

fn problematic() -> impl MyTrait {
    loop {}  // 旧来は () にフォールバックしてOK、新方式では ! を返そうとしてエラー
}

また ? 演算子との組み合わせでも影響が生じる場合があります。

fn old_behavior() -> Result<(), String> {
    let f = || -> ! { loop {} };
    f()?;   // 旧来:() として解釈、新方式:型の不一致が明示化
    Ok(())
}

コンパイラの事前警告と移行パス

Rustコンパイラはこの変更の前から段階的に警告を出しています。cargo fix を活用することで、多くのケースでは自動的に修正できます。また、Rust 2024 editionへの移行ガイドに沿って対応すれば、ほとんどの問題は解決可能です。


Infallible との統合 — 「代替品」の時代が終わる

Infallible はなぜ存在したのか

std::convert::Infallible は、! が安定化されていなかった時代に生まれた回避策です。

// 旧来のパターン:失敗しない変換を表現するために Infallible を使う
impl TryFrom<u8> for MyType {
    type Error = std::convert::Infallible;  // 「絶対にErrにならない」
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        Ok(MyType(value))
    }
}

Infallibleenum Infallible {} として定義された空型であり、意味的には ! と同一です。しかし、コンパイラが ! として認識しないため、最適化や型システムとの連携が不完全でした。

今後は ! の型エイリアスとして再定義

Never型の安定化に伴い、Infallible! の型エイリアスとして再定義されます。

// 将来的な定義(概念的な表現)
pub type Infallible = !;

これにより、既存の Infallible を使ったコードはそのまま動作しながら、コンパイラは内部的に ! として最適化を適用できるようになります。後方互換性を保ちながら、言語設計の一貫性を確保する巧みな移行設計です。


実践:Never型が効くユースケース

ジェネリックエラーハンドリング:Result<T, !>

Never型の実用上の最大の恩恵のひとつが、Result<T, !> という表現です。

use std::convert::Infallible;  // または直接 !(Rust 1.100以降)

// 「絶対にErrを返さない」変換を型で保証
fn parse_always_valid(s: &str) -> Result<String, !> {
    Ok(s.to_uppercase())
}

fn main() {
    let result = parse_always_valid("hello");
    // コンパイラはErr分岐が到達不能と分かるので、matchを簡略化できる
    let value = result.unwrap();  // パニックが起きないことが型レベルで保証
    println!("{}", value);  // "HELLO"
}

これにより、コンパイラは Err 分岐のコードを生成しないよう最適化でき、また開発者は不要なエラーハンドリングを書かずに済みます。

非同期コード:終了しないFutureを型安全に扱う

サーバーのイベントループなど、正常終了しないasync処理を -> ! で表現できます。

use tokio::net::TcpListener;

async fn accept_loop(listener: TcpListener) -> ! {
    loop {
        let (socket, addr) = listener.accept().await.unwrap();
        println!("新しい接続: {}", addr);
        tokio::spawn(async move {
            handle_connection(socket).await;
        });
    }
}

async fn handle_connection(_socket: tokio::net::TcpStream) {
    // 接続処理
}

-> ! を明示することで、この関数が「仕様として終了しない」ことを型で表明でき、誤って return を書いてしまうとコンパイルエラーになります。

トレイト実装の簡素化

! はすべてのトレイトを trivially(自明に)実装できます。値が存在しないため、どんなメソッドも呼ばれることがないからです。これはジェネリックコードのテスト用スタブとして活用できます。

trait Processor {
    fn process(&self, input: i32) -> String;
}

// ! は全トレイトを自明に実装する — テスト用のNoop実装として使用可能
fn process_or_default<P: Processor>(processor: Option<P>, input: i32) -> String {
    match processor {
        Some(p) => p.process(input),
        None => input.to_string(),
    }
}

// T = ! で「Processorが絶対に使われないケース」を型安全に表現
let result = process_or_default::<std::convert::Infallible>(None, 42);

ジェネリックプログラミングにおける型パラメータとしての !

型パラメータに ! を渡せるようになったことが、今回の安定化の核心です。

// ジェネリックな状態機械の例
enum State<E> {
    Running,
    Failed(E),
}

// E = ! → 「絶対に失敗しない状態機械」を型で表現
type InfallibleState = State<!>;  // Rust 1.100以降、直接 ! が使える

fn advance(state: InfallibleState) -> InfallibleState {
    match state {
        State::Running => State::Running,
        State::Failed(e) => match e {},  // この分岐はコンパイラが到達不能と認識
    }
}

match e {} のように空の match アームで Never型の値を「消費」できるのは、Never型の値が実際には生成されないためです。


互換性はどう担保されたか — Craterによる大規模検証

Craterとは

Crater は、crates.io に公開されているクレート全体を対象に、変更前後でコンパイル・テスト結果を比較するRustチームの自動テスト基盤です。言語やコンパイラへの変更が既存のエコシステムにどの程度影響するかを定量的に把握するために使われます。

検証結果の内訳

今回の安定化に際してCraterが実行された結果は以下の通りです。

項目 件数
後退(regression)を検出したクレート 3,277
バックポート対応で修正されたクレート 1,553
実際に完全破壊されたクレート 7

「わずかな破壊性 vs 言語設計の簡潔化」という判断

3,277件の後退のほとんどは、依存しているライブラリの旧バージョンが引き起こすものでした。当該ライブラリの最新版では既に修正済みであり、依存関係を更新することで解決できます。実際に修復不能なレベルで破壊されたのはわずか7クレートです。

Rustチームはこの数字を踏まえ、「長期的な言語設計の簡潔化という利益が、わずかな破壊性を正当化する」と結論付けました。Rustのエコシステムの規模を考えれば、これは非常に慎重かつ丁寧な意思決定プロセスといえます。


よくある疑問(FAQ)

Q. これまで ! はまったく使えなかったのですか?

関数の戻り値型として fn foo() -> ! は以前からNightlyビルドで利用可能でした。今回の安定化で新しく使えるようになったのは主に型パラメータとしての利用T = !Result<T, !> など)と、Infallible との公式な統合です。

Q. 自分のコードは壊れますか?

大多数のコードには影響しません。 影響を受けるのは、型推論フォールバックが変わることで選択されるトレイト実装が変化するケースや、? 演算子と組み合わせた型が曖昧なケースなど、比較的限定的な状況です。コンパイラが事前に警告を出しているので、cargo check を実行して警告に従えば対応できます。

Q. !() はどう使い分けますか?

処理が「完了するが返す値がない」なら ()、「そもそも完了しない・絶対に到達しない」なら ! を使います。副作用だけを行う関数(ファイルへの書き込みなど)の戻り値は ()、無限ループやパニックハンドラの戻り値は ! が適切です。

Q. 今すぐ Rust 2024 edition に移行すべきですか?

Never型の恩恵を最大限に受けるためには、Rust 2024 editionへの移行が推奨されます。型推論フォールバックの新しいルールは2024 edition以降で適用されるため、Cargo.tomledition = "2024" を指定することで、より正確な型推論が得られます。移行には cargo fix --edition が活用できます。ただし、既存プロジェクトの移行は段階的に行い、テストで動作を確認しながら進めることをお勧めします。


まとめ:小さな型がもたらす、大きな設計の簡潔化

本記事の要点3行まとめ

  1. Never型(! は「絶対に値を返さない」ことを型レベルで表す空型であり、型推論・ジェネリクス・エラーハンドリングの設計を根本から改善する。
  2. 10年越しの安定化の最大の難所は型推論フォールバックの変更であり、7,500超クレートのCrater検証を経て互換性を担保した上でRust 1.100に取り込まれた。
  3. Infallible との統合により言語設計の一貫性が確保され、Result<T, !> や型パラメータとしての ! が公式サポートになったことで、ジェネリックプログラミングの表現力が大きく向上した。

Never型は「小さな型記号」ですが、それが正式に型システムに組み込まれることで、コードの意図がより正確に伝わり、コンパイラによる最適化も強化されます。Rustが長年大切にしてきた「正確さ」と「ゼロコスト抽象化」の哲学を、改めて体現した一歩といえるでしょう。

次に読むべきもの


参考資料

  • Stabilizing Rust's never type – LWN.net
  • Stabilize never type (PR #155499) – GitHub rust-lang/rust
  • I stabilized never type – blog.ihatereality.space
  • Stabilize never type – Rust Project Goals 2026
  • Rust never type is stable in 1.100: what changes – TechAIWire

関連記事

IBM Granite の時系列予測モデル「PatchTST-FM-r2」をゼロショットで動かす完全チュートリアル|Apache 2.0 で商用利用OK

IBM Granite の時系列予測モデル「PatchTST-FM-r2」をゼロショットで動かす完全チュートリアル|Apache 2.0 で商用利用OK

IBM Granite の時系列予測モデル「PatchTST-FM-r2」をゼロショットで動かす完全チュートリアル|Apache 2.0 で商用利用OK 学習データを用意しなくても、明日の需要が予測できる時代が来ました。 従来の時系列予測では、ARIMA・Prophet・LSTMといった手法を使うにしても、予測対象のデータセットごとに学習を行う必要がありました。データが少なければ精度は出ず、系列数...

KimiやDeepSeekに話しかけたら、実はClaudeだった──Anthropicが暴いたAI「蒸留攻撃」の全貌

KimiやDeepSeekに話しかけたら、実はClaudeだった──Anthropicが暴いたAI「蒸留攻撃」の全貌

KimiやDeepSeekに話しかけたら、実はClaudeだった──Anthropicが暴いたAI「蒸留攻撃」の全貌 あなたがKimiに送ったあの質問、本当にKimiが答えていましたか? 2026年9月10日、AIスタートアップAnthropicは業界に衝撃を与える「脅威インテリジェンスレポート(2026年9月版)」を公開しました。そのレポートが明らかにしたのは、私たちユーザーが普段から使っている...

Meta Muse 徹底解説|「あなた専用AIエージェント」は個人データをどこまで見ているのか

Meta Muse 徹底解説|「あなた専用AIエージェント」は個人データをどこまで見ているのか メール、カレンダー、決済、健康アプリ——あなたの日常を支えるあらゆるアプリに接続するパーソナルAIエージェントが、2026年9月8日、現実のものになりました。Metaが正式ローンチした「Muse」です。 「便利そう」と感じる方もいれば、「さすがにそこまでは……」と身構える方もいるでしょう。その両方の感覚...

VoiceStudio 音声クローニング完全ガイド|ElevenLabs代替のローカルAIで動画吹替まで【2026年版】

VoiceStudio 音声クローニング完全ガイド|ElevenLabs代替のローカルAIで動画吹替まで【2026年版】

VoiceStudio 音声クローニング完全ガイド|ElevenLabs代替のローカルAIで動画吹替まで【2026年版】 ElevenLabsに毎月$22以上の課金を続けていますか?あるいは、音声データをクラウドに送ることに不安を感じていますか? 本記事では、完全ローカル動作のオープンソース音声スイート「VoiceStudio(OmniVoice Studio)」を使って、3〜15秒の音声から声を...

コメント

0/2000