パターンを使用できるあらゆる場所
Rustではさまざまな場所でパターンが登場し、実はあなたは気づかないうちにそれらをたくさん使ってきました! この節では、パターンが有効になるすべての場所について説明します。
match アーム
第6章で説明したように、match 式のアームではパターンを使います。形式的に言うと、match 式は、キーワード match、照合対象の値、そして1つ以上の match アームから構成されます。各アームは、あるパターンと、その値がそのアームのパターンに一致した場合に実行する式からなります。次のような形です。
match 値 {
パターン => 式,
パターン => 式,
パターン => 式,
}
たとえば、次はリスト6-5の match 式で、変数 x に入っている Option<i32> の値に対してマッチしています。
match x {
None => None,
Some(i) => Some(i + 1),
}
この match 式におけるパターンは、各矢印の左側にある None と Some(i) です。
match 式に対する1つの要件は、それが網羅的でなければならないということです。つまり、match 式の値に対するすべての可能性を考慮しなければなりません。あらゆる可能性をカバーしたことを保証する1つの方法は、最後のアームに残りすべてを受け止めるパターンを置くことです。たとえば、任意の値にマッチする変数名は決して失敗しないため、残っているすべてのケースをカバーします。
特別なパターン _ は何にでもマッチしますが、変数には決して束縛されないため、最後の match アームでよく使われます。たとえば、指定していない値をすべて無視したいときに、_ パターンは便利です。_ パターンについては、この章の後半にある 「パターン内の値を無視する」 でさらに詳しく説明します。
let 文
この章より前では、パターンの利用について match と if let でしか明示的に説明してきませんでした。しかし実際には、let 文を含め、ほかの場所でもパターンを使ってきました。たとえば、次のような単純な let による変数代入を考えてみましょう。
#![allow(unused)]
fn main() {
let x = 5;
}
このような let 文を使うたびに、あなたはパターンを使っていたのです。たとえそれに気づいていなかったとしても! より形式的に言うと、let 文は次のような形をしています。
let パターン = 式;
let x = 5; のように、パターンの位置に変数名がある文では、その変数名は単に特に単純な形のパターンです。Rustはその式をパターンと照合し、見つかった名前をすべて束縛します。したがって、let x = 5; の例では、x は「ここに一致したものを変数 x に束縛する」という意味のパターンです。名前 x がパターン全体であるため、このパターンは実質的に「値が何であっても、すべてを変数 x に束縛する」という意味になります。
let のパターンマッチの側面をより明確に見るために、タプルを分解するために let でパターンを使っているリスト19-1を見てみましょう。
fn main() {
let (x, y, z) = (1, 2, 3);
}
ここでは、タプルをパターンと照合しています。Rustは値 (1, 2, 3) をパターン (x, y, z) と比較し、値がそのパターンに一致すること、つまり両方の要素数が同じであることを確認します。するとRustは 1 を x に、2 を y に、3 を z に束縛します。このタプルパターンは、その内部に3つの個別の変数パターンが入れ子になっているものと考えられます。
パターン内の要素数がタプル内の要素数と一致しない場合、全体の型が一致せず、コンパイラエラーになります。たとえば、リスト19-2は、3要素のタプルを2つの変数に分解しようとする例を示しています。これはうまくいきません。
fn main() {
let (x, y) = (1, 2, 3);
}
このコードをコンパイルしようとすると、次の型エラーになります。
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
このエラーを修正するには、「パターン内の値を無視する」 の節で見るように、_ や .. を使ってタプル内の1つ以上の値を無視できます。問題が、パターン内の変数が多すぎることにあるなら、解決策は変数を取り除いて、変数の数がタプルの要素数と等しくなるようにし、型を一致させることです。
条件付き if let 式
第6章では、if let 式の使い方について、主に1つのケースだけにマッチする match と同等のものをより短く書く方法として説明しました。必要に応じて、if let には、if let 内のパターンが一致しない場合に実行するコードを含む対応する else を付けることもできます。
リスト19-3は、if let、else if、else if let、そして else 式を組み合わせることもできることを示しています。こうすることで、パターンと比較する値を1つしか表現できない match 式よりも柔軟になります。またRustでは、if let、else if、else if let の一連のアームにある条件が、互いに関連している必要もありません。
リスト19-3のコードは、いくつかの条件を順にチェックして、背景を何色にするかを決定します。この例では、実際のプログラムならユーザー入力から受け取るかもしれない値を、ハードコードした変数として用意しています。
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
if let Some(color) = favorite_color {
println!("Using your favorite color, {color}, as the background");
} else if is_tuesday {
println!("Tuesday is green day!");
} else if let Ok(age) = age {
if age > 30 {
println!("Using purple as the background color");
} else {
println!("Using orange as the background color");
}
} else {
println!("Using blue as the background color");
}
}
ユーザーがお気に入りの色を指定していれば、その色が背景として使われます。お気に入りの色が指定されておらず、今日が火曜日なら、背景色は緑になります。それ以外の場合で、ユーザーが自分の年齢を文字列として指定しており、それを数値として正常にパースできれば、その数値の値に応じて色は紫またはオレンジになります。これらの条件のどれにも当てはまらない場合、背景色は青になります。
この条件構造により、複雑な要件に対応できます。ここでのハードコードされた値では、この例は Using purple as the background color と出力します。
if let も、match アームと同じように、既存の変数をシャドーイングする新しい変数を導入できることがわかります。if let Ok(age) = age という行では、Ok バリアントの内側の値を含む新しい age 変数が導入され、既存の age 変数をシャドーイングします。つまり、if age > 30 という条件はそのブロック内に配置する必要があります。この 2 つの条件を if let Ok(age) = age && age > 30 のように組み合わせることはできません。30 と比較したい新しい age は、波かっこによって新しいスコープが始まるまでは有効ではないからです。
if let 式を使う欠点は、match 式とは異なり、コンパイラが網羅性をチェックしないことです。最後の else ブロックを省略して、その結果いくつかのケースの処理を見落としていたとしても、コンパイラはその論理バグの可能性を警告してくれません。
while let 条件付きループ
構文は if let に似ていますが、while let 条件付きループでは、パターンがマッチし続ける限り while ループを実行できます。リスト 19-4 では、スレッド間で送信されるメッセージを待ち受ける while let ループを示していますが、この場合は Option ではなく Result をチェックしています。
fn main() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
for val in [1, 2, 3] {
tx.send(val).unwrap();
}
});
while let Ok(value) = rx.recv() {
println!("{value}");
}
}
この例は 1、2、そして 3 を表示します。recv メソッドはチャネルの受信側から最初のメッセージを取り出し、Ok(value) を返します。第 16 章で recv を初めて見たときは、エラーを直接アンラップするか、for ループを使ってイテレータとして扱いました。しかし、リスト 19-4 が示すように、recv メソッドは送信側が存在する限りメッセージが到着するたびに Ok を返し、送信側が切断されると Err を生成するため、while let を使うこともできます。
for ループ
for ループでは、キーワード for の直後に続く値はパターンです。たとえば、for x in y では、x がパターンです。リスト 19-5 は、for ループの一部としてタプルを分割する、つまり分解するために、for ループでパターンを使う方法を示しています。
fn main() {
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
}
リスト 19-5 のコードは次のように表示します。
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
enumerate メソッドを使ってイテレータを適応し、値とその値のインデックスを、タプルに格納した形で生成するようにしています。最初に生成される値はタプル (0, 'a') です。この値がパターン (index, value) にマッチすると、index は 0、value は 'a' になり、出力の最初の行が表示されます。
関数パラメータ
関数パラメータもパターンにできます。i32 型の x という名前の 1 つのパラメータを取る foo という関数を宣言しているリスト 19-6 のコードは、ここまでで見慣れたものになっているはずです。
fn foo(x: i32) {
// code goes here
}
fn main() {}
x の部分はパターンです! let で行ったのと同様に、関数の引数でタプルをパターンにマッチさせることができます。リスト 19-7 では、タプルの値を関数に渡す際に分割しています。
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
このコードは Current location: (3, 5) を表示します。値 &(3, 5) はパターン &(x, y) にマッチするため、x は値 3、y は値 5 です。
クロージャは関数に似ているため、第 13 章で説明したように、関数パラメータのリストと同じようにクロージャのパラメータリストでもパターンを使うことができます。
ここまでで、パターンを使ういくつかの方法を見てきましたが、パターンは使えるすべての場所で同じように機能するわけではありません。ある場所では、パターンは反駁不可能でなければなりません。別の状況では、反駁可能でもかまいません。次に、この 2 つの概念について説明します。