Rustのエラー E0005: refutable pattern in local binding(失敗しうるパターン)
E0005 は、マッチしないかもしれないパターンを let に書いた、という意味のエラーです。たとえば、value が None かもしれないのに let Some(x) = value; と書いたときです。そうなったときにどうするのかを Rust は知る必要がありますが、ただの let にはそれを書く場所がありません。直し方は、「マッチしなかった」場合の行き先を用意することです。ふつうは let ... else、if let、match のどれかを使います。Option や Result を使い始めたころにとてもよく出会うエラーで、直すのに1分もかかりません。このシリーズのほかのエラーは、Rustのコンパイルエラー解説を見てください。
エラーメッセージ
Section titled “エラーメッセージ”error[E0005]: refutable pattern in local binding --> src/main.rs:4:9 |4 | let Some(top) = best; // ❌ リストが空だったら? | ^^^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch19-02-refutability.html = note: the matched value is of type `Option<i32>`help: you might want to use `let...else` to handle the variant that isn't matched |4 | let Some(top) = best else { todo!() }; // ❌ リストが空だったら? | ++++++++++++++++「refutable pattern in local binding」は「ローカル変数の束縛に、失敗しうる(論駁可能な)パターンがある」という意味です。パターンの下には「None のパターンが扱われていない」とあります。note は「let には、構造体やバリアントが1つだけの列挙型のような、必ずマッチするパターンが必要」「マッチさせた値の型は Option<i32>」と言っていて、help は「let...else でマッチしなかったバリアントを扱ってはどうか」と提案しています。リンク先の章は、日本語版の Book では第18章の「論駁可能性」です。
このエラーが出るのは、次のプログラムです。
fn main() { let scores = vec![72, 95, 88]; let best = scores.iter().max().copied(); let Some(top) = best; // ❌ リストが空だったら? println!("最高スコア: {top}");}ほかのパターンでも同じエラーが出ます。変わるのは「not covered」(扱われていない)の部分だけです。
3 | let Ok(age) = input.parse::<i32>(); | ^^^^^^^ pattern `Err(_)` not covered
3 | let [host, port] = parts.as_slice(); | ^^^^^^^^^^^^ patterns `&[]`, `&[_]` and `&[_, _, _, ..]` not covered1つ目は「Err(_) のパターンが扱われていない」、2つ目は「空のスライス、要素1つのスライス、要素3つ以上のスライスが扱われていない」という意味です。
エラーの意味
Section titled “エラーの意味”カギになる言葉は2つです。
- 論駁可能(refutable)なパターンは、マッチに失敗することがあります。
Some(top)は値がNoneのときに失敗します。Ok(age)はErrのときに失敗します。[host, port]は、スライスの要素がちょうど2つでないときに失敗します。 - 論駁不可能(irrefutable)なパターンは、必ずマッチします。
let top = best;のようなただの名前はいつでも成功しますし、let (x, y) = point;のようなタプルや構造体も同じです。
let は必ず成功しなければなりません。次の行で、let が作った変数を使うからです。もし best が None だったら、次の行の top は何になるのでしょう?答えはありません。だから Rust は止めてくれるのです。「not covered」のラベルには、書き忘れたケースがそのまま並んでいます。None、Err(_)、スライスなら「空」「要素1つ」「要素3つ以上」です。
宅配ボックスを想像してください。let Some(top) = best; は、扉を開けもせずに「荷物は入っているはず」と決めつけて立ち去るようなものです。Rust は、ボックスが空だったらどうするかを決めさせてくれます。
Rust がこうする理由
Section titled “Rust がこうする理由”let、関数の引数、for ループには、失敗しないパターンが必要です。一方、match、if let、while let、let ... else は、失敗しうるパターンを受け付けます。どれも「そうでない場合」を書く場所を持っているからです。これは E0004 と同じルールです。E0004 では match がすべての値を網羅しなければなりませんでした。ここでの「match」は、アームが1つしかない let です。
多くの言語では、存在しない値を読むと null や undefined になったり、実行時にクラッシュしたりします。Rust では、空のリストも、入力欄に打たれたおかしな数字も、見つからない設定も、コードで扱う普通のケースです。Option と Result がそれを目に見えるようにし、E0005 はうっかり飛ばしてしまわないように見張ってくれます。let と関数の引数でのパターンとOptionも見てください。
直し方1:let ... else で早めに抜ける
Section titled “直し方1:let ... else で早めに抜ける”関数の残りの部分がその値を必要としていて、値がなければ何もできることがないときは、これを選びます。rustc の help: が提案しているのもこれです。パターンはそのままにして、else ブロックを足します。このブロックは必ず抜け出さなければなりません。return、break、continue、またはパニックです。
fn port_from(text: &str) -> u16 { // 数値を取り出す。できなければ関数から早めに抜ける let Ok(port) = text.trim().parse::<u16>() else { println!("'{text}' はポート番号として正しくないので、8080 を使います"); return 8080; }; port}
fn main() { let scores = vec![72, 95, 88]; let Some(top) = scores.iter().max() else { println!("まだスコアがありません"); return; }; println!("最高スコア: {top}");
println!("ポート: {}", port_from("3000")); println!("ポート: {}", port_from("abc"));}実行結果:
最高スコア: 95ポート: 3000'abc' はポート番号として正しくないので、8080 を使いますポート: 8080rustc が提案する else { todo!() } を、そのまま残さないようにしましょう。todo!() は実行されるとパニックするので、本物の抜け出し方に置き換えます。詳しくはlet-elseを見てください。
直し方2:どちらのケースでも何かするなら match か if let
Section titled “直し方2:どちらのケースでも何かするなら match か if let”「マッチしなかった」ケースにもやることがあるとき(エラーを表示するなど)や、どちらの場合でも処理を続けるときは、これを選びます。match ならすべてのケースを扱えて、エラーの値も使えます。長さのわからないスライスを分解するのも match の出番です。1つのケースだけが大事なら if let を使います。
fn main() { // match:両方の結果を扱い、エラーも使う for input in ["42", "forty-two"] { match input.parse::<i32>() { Ok(age) => println!("年齢: {age}"), Err(e) => println!("'{input}' を読み取れません: {e}"), } }
// スライスの長さはいろいろ:形でマッチする for address in ["localhost:8080", "localhost"] { let parts: Vec<&str> = address.split(':').collect(); match parts.as_slice() { [host, port] => println!("ホスト = {host}、ポート = {port}"), _ => println!("'{address}' は host:port の形で書いてください"), } }
// if let:値があるときだけ何かする let nickname: Option<&str> = None; if let Some(name) = nickname { println!("こんにちは、{name}さん!"); } else { println!("こんにちは、ゲストさん!"); }}実行結果:
年齢: 42'forty-two' を読み取れません: invalid digit found in stringホスト = localhost、ポート = 8080'localhost' は host:port の形で書いてくださいこんにちは、ゲストさん!「invalid digit found in string」(文字列に無効な数字があった)は、Rust の標準ライブラリが出すエラーメッセージなので英語のままです。_ のアームは、それ以外のすべての長さを受け止めます。これがないと、match は同じ「not covered」のリスト付きで E0004 になります。if letとmatchも見てください。
直し方3:unwrap_or でデフォルト値を使うか、? で呼び出し元に任せる
Section titled “直し方3:unwrap_or でデフォルト値を使うか、? で呼び出し元に任せる”ちょうどいいデフォルト値があるときや、問題を呼び出し元に任せたいときは、これを選びます。unwrap_or(x) は値を返し、値がなければ x を返します。? は Result(または Option)を返す関数の中で使えます。エラーを呼び出し元に返して、関数をそこで終えます。
use std::num::ParseIntError;
// ? はエラーをここで扱わずに、呼び出し元に渡すfn total_minutes(hours: &str, minutes: &str) -> Result<i32, ParseIntError> { let h = hours.parse::<i32>()?; let m = minutes.parse::<i32>()?; Ok(h * 60 + m)}
fn main() { // 何もないときのデフォルト値 let scores: Vec<i32> = Vec::new(); let top = scores.iter().max().copied().unwrap_or(0); println!("最高スコア: {top}");
let volume = "loud".parse::<u8>().unwrap_or(50); println!("音量: {volume}");
println!("{:?}", total_minutes("2", "30")); println!("{:?}", total_minutes("2", "half"));}実行結果:
最高スコア: 0音量: 50Ok(150)Err(ParseIntError { kind: InvalidDigit })値があると確信しているとき(たとえば、コードに自分で書いた数値)は、.expect("理由") でもかまいません。間違っていたら、そのメッセージ付きでパニックします。本当に失敗しえないケースだけに使いましょう。何も返さない main の中で ? を使うと E0277 になります。?演算子と近道:unwrap_orとunwrapを見てください。
直し方4:パターンが失敗しえない型を使う
Section titled “直し方4:パターンが失敗しえない型を使う”データの形がいつも同じで、パターンが失敗しうるのは選んだ型のせいだけ、というときは、これを選びます。スライスや Vec の長さはいろいろですが、[u8; 3] のような配列はいつもちょうど3つの要素を持っています。だから let [r, g, b] = color; は必ずマッチします。タプルや構造体も必ずマッチします。また、split_once は2つに分けた結果を1つのタプルで返すので、長さのわからないスライスではなく、Option を1つ扱うだけで済みます。
struct Point { x: i32, y: i32,}
fn main() { // 配列の長さは決まっている:このパターンは必ずマッチする let color: [u8; 3] = [255, 140, 0]; let [r, g, b] = color; println!("r={r} g={g} b={b}");
// タプルと構造体も必ずマッチする let (host, port) = ("localhost", 8080); let Point { x, y } = Point { x: 3, y: -1 }; println!("{host}:{port}、位置 ({x}, {y})");
// split_once は2つに分けた結果を1つの Option で返す let Some((key, value)) = "theme=dark".split_once('=') else { return; }; println!("{key} -> {value}");}実行結果:
r=255 g=140 b=0localhost:8080、位置 (3, -1)theme -> dark自分で作った列挙型でも同じです。let Shape::Circle(radius) = shape; は pattern `Shape::Square(_)` not covered(Shape::Square(_) のパターンが扱われていない)で失敗します。もし値が本当に円でしかありえないなら、列挙型ではなく Circle という構造体にしたほうがいいかもしれません。配列と構造体も見てください。
よく出会う場面
Section titled “よく出会う場面”Option の for ループ。 for Some(temp) in readings は「refutable pattern in for loop binding」(for ループの束縛に失敗しうるパターンがある)になります。直し方:for temp in readings.iter().flatten() と書けば None を飛ばせます。または、すべての要素でループして、中で if let Some(t) = reading を使います。
関数の引数。 fn greet(Some(name): Option<&str>) は「refutable pattern in function argument」(関数の引数に失敗しうるパターンがある)になります。直し方:name: Option<&str> を受け取って関数の中で match するか、name: &str を受け取って、None のときどうするかは呼び出し側に決めてもらいます。
入力のパース。 ユーザーの入力やコマンドライン引数を読むときの let Ok(n) = line.trim().parse::<i32>(); です。直し方:入力のループの中で let Ok(n) = ... else { println!("数字を入力してください"); continue; }; と書くか、match でエラーを表示します。
関連するエラー
Section titled “関連するエラー”- E0004: non-exhaustive patterns:同じ「not covered」のチェックを、
letではなくmatchで行うエラーです。 - E0308: mismatched types:パターンがそもそも型に合っていない。たとえば
let Some(x) = 5;のようなときです。 - E0277: trait bound not satisfied:ただの
fn main()のように、ResultもOptionも返さない関数の中で?を使ったときに出ます。 - E0282: type annotations needed:Rust がどの型にしたいのかわからないとき、
parse()のそばによく出ます。上の例のように::<i32>を付けましょう。 - コンパイルエラーの一覧:エラー解説ページの全リストです。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. E0005 になる let はどれですか?
2. let ... else の else ブロックは、何をしなければなりませんか?
3. テキストから数値がほしいのですが、数値でなければ 0 で十分です。いちばん短い直し方はどれですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0005 — Rust error codes index(英語):このエラーの公式の説明です。
- 論駁可能性:パターンが合致しないかどうか — The Rust Programming Language 日本語版:rustc がリンクしている章の日本語版です。失敗しうるパターンと失敗しないパターンを説明しています。
- パターンが使用されることのある箇所全部 — The Rust Programming Language 日本語版:
let、if let、while let、forループ、関数の引数でのパターンです。 - let-else — Rust By Example 日本語版:
let ... elseの短い実行できる例が見られます。 - if let — Rust By Example 日本語版:
matchを全部書くよりif letのほうが読みやすいのは、どんなときかがわかります。