コンテンツにスキップ

Rustのエラー E0004: non-exhaustive patterns not covered(matchの網羅漏れ)

E0004 は、match が少なくとも1つの値を扱い忘れていて、その場合に何をすればいいのか Rust が勝手に決めるのを断った、という意味のエラーです。Rust のエラーの中でも、いちばん親切なものの1つです。コンパイラは足りないケースを正確に教えてくれますし、足すべき行まで書いてくれます。このページでは、まず直し方を見せてから、このチェックがなぜ Rust の最高の機能の1つなのかを説明します。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。

error[E0004]: non-exhaustive patterns: `OrderStatus::Delivered` not covered
--> src/main.rs:8:11
|
8 | match status {
| ^^^^^^ pattern `OrderStatus::Delivered` not covered
|
note: `OrderStatus` defined here
--> src/main.rs:1:6
|
1 | enum OrderStatus {
| ^^^^^^^^^^^
...
4 | Delivered, // あとから追加した
| --------- not covered
= note: the matched value is of type `OrderStatus`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
|
10 ~ OrderStatus::Shipped => "on its way",
11 ~ OrderStatus::Delivered => todo!(),
|

「non-exhaustive patterns: OrderStatus::Delivered not covered」は「パターンが網羅されていない。OrderStatus::Delivered が扱われていない」という意味です。help は「ワイルドカードのパターンか、下に示した具体的なパターンのアームを足して、すべてのケースを扱うようにしてください」と提案しています。

このエラーが出るのは、次のプログラムです。

enum OrderStatus {
Pending,
Shipped,
Delivered, // あとから追加した
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "waiting to be packed",
OrderStatus::Shipped => "on its way",
// ❌ Delivered の分がない
}
}
fn main() {
println!("{}", describe(OrderStatus::Delivered));
}

バッククォートの間の文字は、コードによって変わります。`None` not covered、`&_` not covered、`0_u8` and `4_u8..=u8::MAX` not covered、`(true, false)` and `(false, true)` not covered のように表示されることもあります。どれも同じエラーで、match にアームがない値を Rust が並べて見せてくれているのです。

メッセージを上から順に読んでいきましょう。

  1. 8行目: ^^^^^^ は、マッチしている値 status を指しています。
  2. 「pattern OrderStatus::Delivered not covered」: これが扱い忘れたケースです。
  3. note は列挙型(enum)の定義に飛んで、4行目の忘れられたバリアントに印を付けています。
  4. help は、足すべきアームをそのまま見せてくれています。todo!() は仮置きなので、本物のコードに置き換えます。

ボタンが3つあるのに、配線されているのが2つだけの自動販売機を思い浮かべてください。3つ目のボタンを押したら、いったい何が起きるのでしょう?Rust は、押しても何も起きないボタンのある機械を出荷させてくれません。たとえ答えが「故障中」でも、すべてのボタンに答えが必要です。

Rust の match は網羅的(exhaustive)でなければなりません。つまり、その型が取りうるすべての値について、当てはまるアームがちょうど1つある必要があります。アームが足りないと、プログラムは実行すべきコードのない場所にたどり着いてしまいます。ほかの言語では、黙って次へ進んだり、デフォルト値を返したり、実行時にクラッシュしたりします。Rust は、プログラムが動く前にそれを見つけます。基本はmatchのレッスンで説明しています。

本当にありがたいのは、コードが大きくなったあとです。プロジェクトの10か所で match されている列挙型に、新しいバリアントを足したとしましょう。

enum OrderStatus {
Pending,
Shipped,
Delivered,
Returned, // 新しく追加!
}

すると、コンパイラは直す必要のあるすべての match について、E0004 を1つずつ出してくれます。

error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:9:11
error[E0004]: non-exhaustive patterns: `&OrderStatus::Returned` not covered
--> src/main.rs:17:11

どちらも「&OrderStatus::Returned が扱われていない」という意味で、行番号が直すべき場所です。これは、あなたのために書かれた ToDo リストです。コード全体を検索して、全部見つけられたことを祈る必要はありません。経験のある Rust 開発者が列挙型をこれほど気に入っているのは、このためです。怖がらずに変更できるのです。

直し方1:足りないアームを足す

Section titled “直し方1:足りないアームを足す”

自分で作った列挙型なら、これを選びましょう。ほとんどの場合、これが正しい直し方で、rustc の help: が提案しているのもまさにこれです。アームを足して、todo!() を本当にしたい処理に置き換えます。

enum OrderStatus {
Pending,
Shipped,
Delivered,
}
fn describe(status: OrderStatus) -> &'static str {
match status {
OrderStatus::Pending => "waiting to be packed",
OrderStatus::Shipped => "on its way",
OrderStatus::Delivered => "at your door", // ✅ すべてのケースを扱っている
}
}
fn main() {
println!("{}", describe(OrderStatus::Pending));
println!("{}", describe(OrderStatus::Delivered));
}

実行結果:

waiting to be packed
at your door
▶ 実際に試してみよう

(エラーになる例と比べやすいように、ここでは表示する文字列を英語のままにしています。「梱包待ち」「配送中」「お届け済み」という意味です。)

いくつかのバリアントで同じ処理をしたいなら、1つのアームの中で | でつなぎます。OrderStatus::Shipped | OrderStatus::Delivered => false のように書きます。それでも1つずつ名前を書いているので、新しいバリアントが増えたときにはコンパイラがちゃんと教えてくれます。Option でも同じです。`None` not covered と言われたら、None => ... のアームを足しましょう。詳しくはOptionを見てください。

直し方2:数値や文字列には範囲と _ を使う

Section titled “直し方2:数値や文字列には範囲と _ を使う”

整数や char、文字列でマッチするときは、これを選びます。u8 には256通りの値があり、&str に至っては無限にあるので、全部を並べることはできません。数値のまとまりには 1..=9 のような範囲を使い、最後に「それ以外ぜんぶ」として _(または other のような名前)を置きます。

fn grade(score: u8) -> char {
match score {
90..=100 => 'A',
75..=89 => 'B',
50..=74 => 'C',
0..=49 => 'F',
_ => '?', // 101〜255:ありえない点数
}
}
fn run(command: &str) {
match command {
"start" => println!("タイマーを開始します"),
"stop" => println!("タイマーを止めます"),
other => println!("不明なコマンド: {other}"),
}
}
fn main() {
println!("{} {} {}", grade(95), grade(62), grade(150));
run("start");
run("pause");
}

実行結果:

A C ?
タイマーを開始します
不明なコマンド: pause
▶ 実際に試してみよう

文字列の場合、rustc ははっきりとこう言います。`&str` cannot be matched exhaustively, so a wildcard `_` is necessary(&str はすべてを網羅できないので、ワイルドカード _ が必要)。数値の場合、Rust は範囲を正確にチェックします。範囲が本当にすべての値をカバーしているなら(たとえば 0..=49 から 90..=u8::MAX まで)、_ はまったくいりません。詳しくは範囲でマッチするを見てください。

直し方3:1つのケースだけ気にするなら if let か let else を使う

Section titled “直し方3:1つのケースだけ気にするなら if let か let else を使う”

1つのパターンのときだけ何かをして、残りは無視したいときは、これを選びます。空の「何もしない」アームを書いた match でも動きますが、if let のほうが意図がはっきり伝わります。let else は「値を取り出す。取り出せなければ関数を早めに抜ける」ためのものです。

fn greet(nickname: Option<&str>) {
if let Some(name) = nickname {
println!("こんにちは、{name}さん!");
}
// ニックネームがない:何もしない
}
fn first_word_len(text: &str) -> usize {
let Some(word) = text.split_whitespace().next() else {
return 0; // 空のテキスト:早めに抜ける
};
word.len()
}
fn main() {
greet(Some("Ferris"));
greet(None);
println!("{}", first_word_len("hello world"));
println!("{}", first_word_len(" "));
}

実行結果:

こんにちは、Ferrisさん!
5
0
▶ 実際に試してみよう

(first_word_len は文字列のバイト数を数えるので、"hello world" は英語のままにしています。)

let else の else ブロックは、今のブロックから必ず抜け出さなければなりません(return、break、continue、またはパニック)。詳しくはif letとlet-elseを見てください。

直し方4:残りを _ => でまとめて受ける(ただし代償あり)

Section titled “直し方4:残りを _ => でまとめて受ける(ただし代償あり)”

列挙型のほとんどのバリアントで、本当に同じ処理をするときは、これを選びます。_ は、それより上のアームに当てはまらなかったものすべてにマッチします。

enum Key {
Up,
Down,
Left,
Right,
Enter,
Escape,
}
fn handle(key: Key) {
match key {
Key::Enter => println!("決定"),
Key::Escape => println!("キャンセル"),
_ => println!("カーソルを動かす"),
}
}
fn main() {
handle(Key::Enter);
handle(Key::Left);
handle(Key::Escape);
}

実行結果:

決定
カーソルを動かす
キャンセル
▶ 実際に試してみよう

代償についても正直に言っておきます。_ を使うと、Rust がこうする理由で紹介したチェックが働かなくなります。あとで Key::Delete を足しても、コンパイラは何も言いません。そして Delete キーを押すとカーソルが動いてしまいます。Rust が見つけてくれたはずのバグです。自分の列挙型では、| でバリアントを並べるほうを選びましょう(Key::Up | Key::Down | Key::Left | Key::Right => ...)。_ は、数値、文字列、ほかのクレートの列挙型のためにとっておきましょう。

None のない Option。 match nickname { Some(name) => ... } と書くと `None` not covered になります。直し方:None => のアームを足すか、None のときにすることがないなら if let Some(name) = nickname を使います。

タプルと真偽値。 2つの bool を一度にマッチすると組み合わせは4通りあり、いくつか忘れがちです。

match (logged_in, is_admin) {
(true, true) => println!("Welcome, admin"),
(false, false) => println!("Please log in"),
} // ❌ patterns `(true, false)` and `(false, true)` not covered

コメントのメッセージは「(true, false) と (false, true) のパターンが扱われていない」という意味です。直し方:足りないアームを足すか、タプルの中で _ を使います。たとえば (false, _) => println!("Please log in") と書けば、「ログインしていない」2つのケースをまとめて扱えます。詳しくは複数の値をまとめてマッチするを見てください。

let のパターン。 let Some(score) = saved; は、マッチしないかもしれない let です。これは同じ考え方の兄弟エラー、E0005 になります。

error[E0005]: refutable pattern in local binding
--> src/main.rs:3:9
|
3 | let Some(score) = saved;
| ^^^^^^^^^^^ pattern `None` not covered
|
= note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant
help: you might want to use `let...else` to handle the variant that isn't matched
|
3 | let Some(score) = saved else { todo!() };
| ++++++++++++++++

「refutable pattern in local binding」は「ローカル変数の束縛に、失敗しうる(論駁可能な)パターンがある」という意味です。note は「let には、構造体やバリアントが1つだけの列挙型のような、必ずマッチするパターンが必要」、help は「let...else でマッチしなかったバリアントを扱ってはどうか」と言っています。直し方:let Some(score) = saved else { return; }; と書くか、直し方3のように if let を使います。詳しくはlet と関数の引数でのパターンを見てください。

ほかのクレートの列挙型。 std::io::ErrorKind のように、#[non_exhaustive] が付いた列挙型があります。作者があとでバリアントを足すかもしれないので、今あるバリアントを全部並べても、rustc は `_` not covered と言います。直し方:_ => のアームを足します。この場合は手抜きではなく、必須です。

  • E0005(refutable pattern in local binding):同じ「not covered」のチェックを、match ではなく let に対して行うエラー。上で紹介しました。
  • E0308:型の不一致。たとえば、match のアームが違う型を返しているとき(あるアームでは "text"、別のアームでは数値)。
  • E0382:borrow of partially moved value(一部がムーブされた値の借用)。Some(name) のようなアームが String をムーブして取り出したあとで、元の値を使ったとき。代わりに &nickname でマッチしましょう。
  • E0507:借用している値からムーブできない。借りているだけのものから、match のアームが String を取り出そうとしたとき。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. 列挙型に Refunded というバリアントを足しました。すべてのバリアントを名前で並べている既存の match 式に対して、コンパイラはどうしますか?

2. &str を "yes" と "no" でマッチしています。E0004 はどう直しますか?

3. 自分の列挙型に対する match で _ => を使う欠点は何ですか?

スコア: 0 / 3