コンテンツにスキップ

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のコンパイルエラー解説を見てください。

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 covered

1つ目は「Err(_) のパターンが扱われていない」、2つ目は「空のスライス、要素1つのスライス、要素3つ以上のスライスが扱われていない」という意味です。

カギになる言葉は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 は、ボックスが空だったらどうするかを決めさせてくれます。

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 を使います
ポート: 8080
▶ 実際に試してみよう

rustc が提案する 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
音量: 50
Ok(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=0
localhost:8080、位置 (3, -1)
theme -> dark
▶ 実際に試してみよう

自分で作った列挙型でも同じです。let Shape::Circle(radius) = shape; は pattern `Shape::Square(_)` not covered(Shape::Square(_) のパターンが扱われていない)で失敗します。もし値が本当に円でしかありえないなら、列挙型ではなく Circle という構造体にしたほうがいいかもしれません。配列と構造体も見てください。

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 でエラーを表示します。

理解度チェック

1. E0005 になる let はどれですか?

2. let ... else の else ブロックは、何をしなければなりませんか?

3. テキストから数値がほしいのですが、数値でなければ 0 で十分です。いちばん短い直し方はどれですか?

スコア: 0 / 3