パターンが使用されることのある箇所全部
Rustにおいて、パターンはいろんな箇所に出現し、そうと気づかないうちにたくさん使用してきました! この節は、パターンが合法な箇所全部を議論します。
match
アーム
第6章で議論したように、パターンをmatch
式のアームで使います。正式には、match
式はキーワードmatch
、
マッチ対象の値、パターンとそのアームのパターンに値が合致したら実行される式からなる1つ以上のマッチアームとして定義されます。
以下のように:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
match
式の必須事項の1つは、match
式の値の可能性全てが考慮されなければならないという意味で網羅的である必要があることです。
全可能性をカバーしていると保証する1つの手段は、最後のアームに包括的なパターンを入れることです:
例えば、どんな値にも合致する変数名は失敗することがあり得ないので、故に残りの全ケースをカバーできます。
_
という特定のパターンは何にでもマッチしますが、変数には束縛されないので、よく最後のマッチアームに使用されます。
例えば、_
パターンは、指定されていないあらゆる値を無視したい時に有用です。
_
パターンについて詳しくは、この章の後ほど、「パターンで値を無視する」節で講義します。
条件分岐if let
式
第6章で主にif let
式を1つの場合にしか合致しないmatch
と同様のものを書く省略法として使用する方法を議論しました。
オプションとして、if let
にはif let
のパターンが合致しない時に走るコードを含む対応するelse
も用意できます。
リスト18-1は、if let
、else if
、else if let
式を混ぜてマッチさせることもできることを示しています。
そうすると、パターンと1つの値しか比較することを表現できないmatch
式よりも柔軟性が高くなります。
また、一連の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(); if let Some(color) = favorite_color { // あなたのお気に入りの色、{}を背景色に使用します println!("Using your favorite color, {}, as the background", color); } 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
と出力するでしょう。
match
アームのようにif let
もシャドーイングされた変数を導入できることがわかります:
if let Ok(age) = age
の行は、Ok
列挙子の中の値を含むシャドーイングされた新しい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
ループを走らせます。
リスト18-2の例は、ベクタをスタックとして使用するwhile let
ループを示し、
ベクタの値をプッシュしたのとは逆順に出力します:
#![allow(unused)] fn main() { let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); } }
この例は、3, 2, そして1と出力します。pop
メソッドはベクタの最後の要素を取り出してSome(value)
を返します。
ベクタが空なら、pop
はNone
を返します。while
ループはpop
がSome
を返す限り、ブロックのコードを実行し続けます。
pop
がNone
を返すと、ループは停止します。while let
を使用してスタックから全ての要素を取り出せるのです。
for
ループ
第3章で、Rustコードにおいては、for
ループが最もありふれたループ構造だと述べましたが、
for
が取るパターンについてはまだ議論していませんでした。for
ループにおいて、
直接キーワードfor
に続く値がパターンなので、for x in y
では、x
がパターンになります。
リスト18-3はfor
ループでパターンを使用してfor
ループの一部としてタプルを分配あるいは、分解する方法をデモしています。
#![allow(unused)] fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); } }
リスト18-3のコードは、以下のように出力するでしょう:
a is at index 0
b is at index 1
c is at index 2
enumerate
メソッドを使用してイテレータを改造し、値とその値のイテレータでの添え字をタプルに配置して生成しています。
enumerate
の最初の呼び出しは、タプル(0, 'a')
を生成します。この値がパターン(index, value)
とマッチさせられると、
index
は0
、value
は'a'
になり、出力の最初の行を出力するのです。
let
文
この章に先駆けて、match
とif let
でパターンを使用することだけ明示的に議論してきましたが、
実はlet
文を含む他の箇所でもパターンを使用してきたのです。例として、このlet
での率直な変数代入を考えてください:
#![allow(unused)] fn main() { let x = 5; }
この本を通してこのようなlet
を何百回も使用してきて、お気付きではなかったかもしれませんが、
パターンを使用していたのです!より正式には、let
文はこんな見た目をしています:
let PATTERN = EXPRESSION;
let x = 5;
のような変数名がPATTERN
スロットにある文で、変数名は、ただ特に単純な形態のパターンなのです。
Rustは式をパターンと比較し、見つかったあらゆる名前を代入します。故に、let x = 5;
の例では、
x
は「ここでマッチしたものを変数x
に束縛する」ことを意味するパターンです。
名前x
がパターンの全容なので、このパターンは実質的に「値が何であれ、全てを変数x
に束縛しろ」を意味します。
let
のパターンマッチングの観点をよりはっきり確認するためにリスト18-4を考えてください。
これはlet
でパターンを使用し、タプルを分配します。
#![allow(unused)] fn main() { let (x, y, z) = (1, 2, 3); }
ここでタプルに対してパターンをマッチさせています。Rustは値(1, 2, 3)
をパターン(x, y, z)
と比較し、
値がパターンに合致すると確認するので、1
をx
に、2
をy
に、3
をz
に束縛します。
このタプルパターンを個別の3つの変数パターンが内部にネストされていると考えることもできます。
パターンの要素数がタプルの要素数と一致しない場合、全体の型が一致せず、コンパイルエラーになるでしょう。 例えば、リスト18-5は、3要素のタプルを2つの変数に分配しようとしているところを表示していて、動きません。
let (x, y) = (1, 2, 3);
このコードのコンパイルを試みると、このような型エラーに落ち着きます:
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ expected a tuple with 3 elements, found one with 2 elements
| (3要素のタプルを予期したのに、2要素のタプルが見つかりました)
|
= note: expected type `({integer}, {integer}, {integer})`
found type `(_, _)`
タプルの値のうち1つ以上を無視したかったら、「パターンで値を無視する」節で見かけるように、
_
か..
を使用できるでしょう。パターンに変数が多すぎるというのが問題なら、変数の数がタプルの要素数と一致するように変数を減らすことで、
型を一致させることが解決策です。
関数の引数
関数の引数もパターンにできます。リスト18-6のコードは、型i32
のx
という引数1つを取るfoo
という関数を宣言していますが、
これまでに馴染み深くなっているはずです。
#![allow(unused)] fn main() { fn foo(x: i32) { // コードがここに来る // code goes here } }
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); }
このコードはCurrent location: (3, 5)
と出力します。値&(3, 5)
はパターン&(x, y)
と合致するので、
x
は値3
、y
は値5
になります。
また、クロージャの引数リストでも、関数の引数リストのようにパターンを使用することができます。 第13章で議論したように、クロージャは関数に似ているからです。
この時点で、パターンを使用する方法をいくつか見てきましたが、パターンを使用できる箇所全部で同じ動作をするわけではありません。 パターンが論駁不可能でなければならない箇所もあります。他の状況では、論駁可能にもなり得ます。この2つの概念を次に議論します。