はじめに
パターンを使用できるすべての場所へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。
この実験では、Rust でパターンを使用できるすべての場所を探ります。
パターンを使用できるすべての場所
Rust では、パターンが多数の場所に登場します。ご存知なしにたくさん使ってきたことになります!このセクションでは、パターンが有効なすべての場所について説明します。
match のアーム
第 6 章で説明したように、match 式のアームでパターンを使用します。形式的には、match 式はキーワード match、照合対象の値、および 1 つ以上のマッチ アームで構成されます。マッチ アームは、パターンと、値がそのアームのパターンに一致した場合に実行する式です。次のようになります。
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
たとえば、次はリスト 6-5 の match 式で、変数 x の Option<i32> 値を照合しています。
match x {
None => None,
Some(i) => Some(i + 1),
}
この match 式のパターンは、各矢印の左側の None と Some(i) です。
match 式の要件の 1 つは、match 式の値のすべての可能性を網羅するという意味で、網羅的である必要があることです。すべての可能性を網羅したことを確認する 1 つの方法は、最後のアームに catchall パターンを持つことです。たとえば、任意の値と一致する変数名は失敗することがないため、残りのすべてのケースを網羅します。
特定のパターン _ は何でも一致しますが、変数にバインドすることはありません。したがって、最後のマッチ アームでよく使用されます。たとえば、指定されていない値を無視したい場合、_ パターンは便利です。「パターン内の値の無視」で _ パターンについて詳しく説明します。
条件付き if let 式
第 6 章では、主に 1 つのケースのみを照合する match と同等のものを書くための短い方法として if let 式をどのように使用するかについて説明しました。任意で、if let には、if let のパターンが一致しない場合に実行するコードを含む対応する else を持つことができます。
リスト 18-1 に示すように、if let、else if、および else if let 式を組み合わせることも可能です。これにより、パターンと比較する値を 1 つだけ表現できる match 式よりも柔軟性が高くなります。また、Rust では、一連の if let、else if、および else if let アームの条件が互いに関連している必要はありません。
リスト 18-1 のコードは、いくつかの条件を一連のチェックに基づいて背景色を決定します。この例では、実際のプログラムがユーザー入力から受け取る可能性のあるハードコードされた値を持つ変数を作成しました。
ファイル名:src/main.rs
fn main() {
let favorite_color: Option<&str> = None;
let is_tuesday = false;
let age: Result<u8, _> = "34".parse();
1 if let Some(color) = favorite_color {
2 println!(
"Using your favorite, {color}, as the background"
);
3 } else if is_tuesday {
4 println!("Tuesday is green day!");
5 } else if let Ok(age) = age {
6 if age > 30 {
7 println!("Using purple as the background color");
} else {
8 println!("Using orange as the background color");
}
9 } else {
10 println!("Using blue as the background color");
}
}
リスト 18-1: if let、else if、else if let、および else の組み合わせ
ユーザーがお気に入りの色を指定している場合 [1]、その色が背景色として使用されます [2]。お気に入りの色が指定されておらず、今日が火曜日の場合 [3]、背景色は緑になります [4]。それ以外の場合、ユーザーが年齢を文字列として指定し、それを数値として正常に解析できる場合 [5]、数値の値に応じて色は紫色 [7] またはオレンジ色 [8] になります [6]。これらの条件がすべて当てはまらない場合 [9]、背景色は青色になります [10]。
この条件付き構造により、複雑な要件に対応できます。ここではハードコードされた値を使用していますが、この例では Using purple as the background color が表示されます。
if let 式を使用する欠点は、コンパイラが網羅性をチェックしないことです。一方、match 式の場合はチェックします。最後の else ブロック [9] を省略し、いくつかのケースを処理しないままにした場合、コンパイラは潜在的な論理バグについて警告しません。
while let 条件付きループ
if let と構造が似ていますが、while let 条件付きループは、パターンが引き続き一致する限り、while ループを実行できるようにします。リスト 18-2 では、while let ループをコード化しています。このループでは、ベクトルをスタックとして使用し、ベクトルに格納された値を押し込まれた順序とは逆の順序で表示します。
ファイル名:src/main.rs
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{top}");
}
リスト 18-2: stack.pop() が Some を返す限り、while let ループを使用して値を表示する
この例では、3、2、そして 1 が表示されます。pop メソッドはベクトルから最後の要素を取り出し、Some(value) を返します。ベクトルが空の場合、pop は None を返します。while ループは、pop が Some を返す限り、そのブロック内のコードを継続して実行します。pop が None を返すと、ループは停止します。while let を使用することで、スタックからすべての要素を取り出すことができます。
for ループ
for ループでは、キーワード for の直後の値はパターンです。たとえば、for x in y では、x がパターンです。リスト 18-3 は、for ループの一部としてタプルを分解するために、for ループでパターンを使用する方法を示しています。
ファイル名:src/main.rs
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{value} is at index {index}");
}
リスト 18-3: for ループでパターンを使用してタプルを分解する
リスト 18-3 のコードは、次のように表示されます。
a is at index 0
b is at index 1
c is at index 2
enumerate メソッドを使用して反復子を調整することで、その値とその値のインデックスをタプルに格納した値を生成します。生成される最初の値はタプル (0, 'a') です。この値がパターン (index, value) と一致すると、index は 0 になり、value は 'a' になり、出力の最初の行が表示されます。
let 文
この章の前では、match と if let でパターンを明示的に使用することについてのみ説明してきましたが、実際には match や if let 以外の場所でもパターンを使用しています。たとえば、let を使った簡単な変数代入を考えてみましょう。
let x = 5;
このような let 文を使ったことがあれば、パターンを使っていたことになります。ただし、それに気付いていなかったかもしれません!もう少し形式的に言うと、let 文は次のようになります。
let PATTERN = EXPRESSION;
let x = 5; のような、PATTERN のスロットに変数名がある文では、変数名はパターンの特別に単純な形式にすぎません。Rust は式をパターンと比較し、見つけた名前を割り当てます。したがって、let x = 5; の例では、x は「ここで一致するものを変数 x にバインドする」というパターンです。名前 x が全体のパターンであるため、このパターンは実質的に「値が何であれ、すべてを変数 x にバインドする」という意味になります。
let のパターンマッチングの側面をもっと明確に見るために、リスト 18-4 を見てみましょう。この例では、let にパターンを使ってタプルを分解しています。
let (x, y, z) = (1, 2, 3);
リスト 18-4: パターンを使ってタプルを分解し、一度に 3 つの変数を作成する
ここでは、タプルをパターンと比較しています。Rust は値 (1, 2, 3) をパターン (x, y, z) と比較し、要素数が同じであることから値がパターンに一致することを確認します。そのため、Rust は 1 を x に、2 を y に、3 を z にバインドします。このタプルパターンは、その中に 3 つの個々の変数パターンをネストしていると考えることができます。
パターンの要素数がタプルの要素数と一致しない場合、全体の型が一致せず、コンパイラエラーが発生します。たとえば、リスト 18-5 は、3 つの要素を持つタプルを 2 つの変数に分解しようとした例で、うまくいきません。
let (x, y) = (1, 2, 3);
リスト 18-5: タプルの要素数と一致しない変数を持つパターンを誤って構築する
このコードをコンパイルしようとすると、次のような型エラーが発生します。
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 `(_, _)`
エラーを修正するには、「パターン内の値の無視」で見るように、_ や .. を使ってタプルの 1 つ以上の値を無視することができます。パターンに変数が多すぎる問題がある場合、解決策は変数を削除して型を一致させることで、変数の数がタプルの要素数と一致するようにすることです。
関数のパラメータ
関数のパラメータもパターンにすることができます。型 i32 の名前が x の 1 つのパラメータを持つ foo という名前の関数を宣言するリスト 18-6 のコードは、これまでに見たものと似ているはずです。
fn foo(x: i32) {
// code goes here
}
リスト 18-6: パラメータにパターンを使用する関数のシグネチャ
x の部分はパターンです!let と同じように、関数の引数のタプルをパターンに一致させることができます。リスト 18-7 は、タプルを関数に渡す際にその値を分割します。
ファイル名:src/main.rs
fn print_coordinates(&(x, y): &(i32, i32)) {
println!("Current location: ({x}, {y})");
}
fn main() {
let point = (3, 5);
print_coordinates(&point);
}
リスト 18-7: タプルを分解するパラメータを持つ関数
このコードは Current location: (3, 5) を表示します。値 &(3, 5) はパターン &(x, y) に一致するため、x は値 3 で、y は値 5 です。
クロージャのパラメータリストでも、第 13 章で説明したように関数と同じようにパターンを使用することができます。クロージャは関数に似ているためです。
この時点で、パターンを使用するいくつかの方法を見てきましたが、パターンは使用できるすべての場所で同じように機能するわけではありません。いくつかの場所では、パターンは反駁不可能でなければなりません。他の状況では、反駁可能です。次に、これらの 2 つの概念について説明します。
まとめ
おめでとうございます!あなたは「パターンを使用できるすべての場所」の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を行って練習してください。