Rustの「網羅的でないパターン」(E0004)を修正する:`match`が全ケースをカバーしない場合

intermediate🦀 Rust2026-03-27

コンテキスト: Rustにおける「非網羅的なパターン (non-exhaustive patterns)」の理解

Rustのmatch式は、制御フローとパターンマッチングにおける基本的な機能です。その最も強力な側面の一つであり、時には戸惑うこともあるのが、網羅性チェックです。

これは、matchを使用する際に、コンパイラが、マッチ対象の型が取りうる可能性のあるすべての値またはバリアントを処理することを要求するという意味です。これは単なる奇妙な言語ルールではなく、極めて重要な安全機構です。

すべてのケースを考慮することを強制することで、Rustは潜在的な実行時バグの防止に役立ちます。例えば、エラー状態や新しいenumバリアントの処理を忘れると、他の言語では予期せぬ動作やパニックにつながる可能性があります。Rustは、コードが堅牢かつ明示的であることを保証します。

このエラーは通常、enumに対してマッチングを行う際に発生します。複数のバリアントを持つenumを定義し、matchステートメントを記述した後、あなたまたはチームメイトがそのenumに新しいバリアントを追加する場合があります。再コンパイルすると、Rustは既存のmatchステートメントが不完全であることを通知します。

問題: error[E0004]: non-exhaustive patterns: SomeVariant not covered

このエラーが発生する一般的なシナリオを見てみましょう。タスクのステータスを表すenumがあると想像してください。

enum TaskStatus {
    Pending,
    InProgress,
    Completed,
}

fn print_status(status: TaskStatus) {
    match status {
        TaskStatus::Pending => println!("Task is pending."),
        TaskStatus::InProgress => println!("Task is in progress."),
        // TaskStatus::Completed がここにありません!
    }
}

fn main() {
    let my_task_status = TaskStatus::Completed;
    print_status(my_task_status);
}

このコードをコンパイルしようとすると、エラーが発生します。

error[E0004]: non-exhaustive patterns: `Completed` not covered
 --> src/main.rs:7:11
  |
7 |     match status {
  |           ^^^^^ pattern `TaskStatus::Completed` not covered
  |
  = help: ensure that all possible cases are covered, perhaps with `_` or `..`

エラーメッセージは非常に明確です: non-exhaustive patterns: Completed not covered。問題のあるmatchステートメントの正確な行を指し示し、修正方法まで提案しています。

デバッグプロセス: 不足しているパターンの特定

E0004に関する朗報は、デバッグが通常は簡単であることです。コンパイラは、不足しているバリアントやパターンを文字通り名前で示し、何が問題であるかを正確に伝えます。

- **エラーメッセージを注意深く読む:** `non-exhaustive patterns: `Completed` not covered`という行は、どの特定のバリアント(例では`Completed`)が`match`式によって処理されていないかを直接示しています。
- **`match`ステートメントを見つける:** エラーはまた、不完全な`match`ステートメントがどこにあるか、ファイル名と行番号(例: `--> src/main.rs:7:11`)を提供します。
- **enum定義を調べる:** `match`アームと、マッチング対象の`enum`の完全な定義を相互参照します。不足しているバリアントをすぐに見つけることができます。

私たちの例では、TaskStatus enumにはPendingInProgressCompletedがあります。matchステートメントはPendingInProgressのみを処理しており、Completedが明らかに不足しています。

解決策: 網羅的なマッチングの保証

意図に応じて、これを解決する方法はいくつかあります。

オプション1: 不足しているすべてのバリアントを明示的に追加する

これは最も直接的で、しばしば推奨される解決策です。enumのすべてのバリアントを具体的に処理するつもりなら、不足しているアームをmatchステートメントに追加するだけです。

enum TaskStatus {
    Pending,
    InProgress,
    Completed,
    // 後で新しい 'OnHold' バリアントが追加されたかもしれません?
    // OnHold,
}

fn print_status(status: TaskStatus) {
    match status {
        TaskStatus::Pending => println!("Task is pending."),
        TaskStatus::InProgress => println!("Task is in progress."),
        TaskStatus::Completed => println!("Task is completed."), // <-- この行を追加
        // 'OnHold' が追加された場合、次のように追加します:
        // TaskStatus::OnHold => println!("Task is on hold."),
    }
}

fn main() {
    let my_task_status = TaskStatus::Completed;
    print_status(my_task_status);
}

TaskStatus::Completedアームが追加されたことで、matchは網羅的になり、コードは正常にコンパイルされるようになります。

オプション2: 処理されないケースにワイルドカードパターン (_) を使用する

場合によっては、すべてのバリアントに気を使わない、または明示的にリストしていないケースに対してデフォルトのアクションを提供したいことがあります。ここでワイルドカードパターン_が役立ちます。

enum TaskStatus {
    Pending,
    InProgress,
    Completed,
    OnHold, // これが追加されたと想像してみましょう。まだ具体的に気にする必要はありません。
}

fn print_status(status: TaskStatus) {
    match status {
        TaskStatus::Pending => println!("Task is pending."),
        TaskStatus::InProgress => println!("Task is in progress."),
        _ => println!("Task is in an unknown or unhandled state."), // <-- Completed、OnHold、および将来のすべてのバリアントをキャッチ
    }
}

fn main() {
    let my_task_status = TaskStatus::Completed;
    print_status(my_task_status);

    let another_status = TaskStatus::OnHold;
    print_status(another_status);
}

_パターンはキャッチオールとして機能します。これは、先行するパターンによってマッチされなかったすべてのバリアントにマッチします。これは次の場合に役立ちます。

- デフォルトのアクションを提供する。
- 特定のロジックを必要としない特定のバリアントを無視する。
- 新しいenumバリアントに対する将来性を確保し、すぐにコンパイルエラーにならないようにする。

トレードオフとして、enumに新しいバリアントを追加した場合、_を使用しているとコンパイラは警告しません。実際には特定の処理を意図していたとしても、デフォルトのロジックでサイレントに処理されてしまう可能性があります。_は慎重に使用してください。

オプション3: #[non_exhaustive]による将来性確保 (ライブラリ作成者向け)

この解決策はより高度です。主に、enumを公開するライブラリやクレートのようなパブリックAPIを設計する際に重要です。将来のライブラリバージョンでenumに新しいバリアントを追加する予定があり、それに対してmatchを使用しているダウンストリームユーザーに対する破壊的変更を避けたい場合、enumを#[non_exhaustive]としてマークすることができます。

#[non_exhaustive]
pub enum Event {
    KeyPressed(char),
    MouseClick { x: i32, y: i32 },
    TimerElapsed,
    // 将来、'WindowResized'のようなバリアントを追加するかもしれません
}

// ダウンストリームクレートのコード
fn handle_event(event: Event) {
    match event {
        Event::KeyPressed(key) => println!("Key pressed: {}", key),
        Event::MouseClick { x, y } => println!("Mouse clicked at ({}, {})", x, y),
        // Event::TimerElapsed => println!("Timer elapsed."), // 明示的に処理
        _ => println!("Unhandled event."), // <-- non_exhaustive enumには必須
    }
}

enumが#[non_exhaustive]とマークされている場合、それに対してマッチングを行うコードは、将来の潜在的なバリアントを処理するためにワイルドカードパターン (_) を含める必要があります。含まれていない場合、E0004エラーが発生し、将来の互換性を考慮するように強制されます。これにより、ワイルドカードアームが含まれていれば、新しいバリアントを追加したときにライブラリの更新がすぐにユーザーのコードを破壊するのを防ぐことができます。

検証: 修正の確認

いずれかの解決策を適用したら、修正の確認は簡単です。

- **コードをコンパイルする:** `cargo build`または`rustc your_file.rs`を実行します。エラーがなくなっていれば、網羅性の問題は正常に解決されています。
- **アプリケーション/テストを実行する:** プログラムまたはそのテストスイートを実行して、新しい`match`ロジックが期待どおりに動作し、新しい実行時問題を引き起こさないことを確認します。

主な目標は、コンパイラがパスすることであり、これによりRustのルールに従ってmatchステートメントが必要なすべてのケースをカバーしていることが示されます。

学んだ教訓と予防策

Rustの網羅的なマッチングは、潜在的なバグを早期に発見する強力な機能です。これを受け入れることは、より堅牢なコードを書くことを意味します。以下にいくつかの重要なポイントを示します。

- **`match`ステートメントのレビュー:** `enum`を変更するたびに(特に新しいバリアントを追加する場合)、その`enum`を使用するすべての`match`ステートメントをレビューすることを常に忘れないでください。忘れてもコンパイラが教えてくれますが、事前に対応することが役立ちます。
- **`_`の戦略的な使用:** ワイルドカードパターン`_`は、デフォルトのケースや無関係なバリアントを無視する場合に役立ちます。ただし、その影響を理解してください。将来のバリアント追加を隠蔽するため、デフォルトのアクションが本当に許容される場所で使用してください。
- **パブリックAPIには`#[non_exhaustive]`を検討する:** ライブラリを公開する場合、enumを`#[non_exhaustive]`とマークすることで、ライブラリを更新する際にユーザーの多くの頭痛の種を軽減できます。これは、enumのバリアントが拡張される可能性があることを示す優れた方法です。

Rustのコンパイラはenumの網羅的なパターンマッチングの主要なツールですが、パターンを理解することはプログラミングにおけるより広範なスキルです。例えば、テキストデータや複雑な正規表現を扱う場合、ToolCraftのRegex Testerのようなツールが、正規表現パターンのライブテストとデバッグに非常に役立つでしょう。これは異なる種類のパターンですが、パターンが必要なものをカバーしているか、または除外すべきものを除外しているかを保証するという原則は中心的なままです。

Related Error Notes