match
制御フロー演算子
Rustには、一連のパターンに対して値を比較し、マッチしたパターンに応じてコードを実行させてくれるmatch
と呼ばれる、
非常に強力な制御フロー演算子があります。パターンは、リテラル値、変数名、ワイルドカードやその他多数のもので構成することができます;
第18章で、全ての種類のパターンと、その目的については解説します。match
のパワーは、
パターンの表現力とコンパイラが全てのありうるパターンを処理しているかを確認してくれるという事実に由来します。
match
式をコイン並べ替え装置のようなものと考えてください: コインは、様々なサイズの穴が空いた通路を流れ落ち、
各コインは、サイズのあった最初の穴に落ちます。同様に、値はmatch
の各パターンを通り抜け、値が「適合する」最初のパターンで、
値は紐付けられたコードブロックに落ち、実行中に使用されるわけです。
コインについて話したので、それをmatch
を使用する例にとってみましょう!数え上げ装置と同じ要領で未知のアメリカコインを一枚取り、
どの種類のコインなのか決定し、その価値をセントで返す関数をリスト6-3で示したように記述することができます。
#![allow(unused)] fn main() { enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } }
value_in_cents
関数内のmatch
を噛み砕きましょう。まず、match
キーワードに続けて式を並べています。
この式は今回の場合、値coin
です。if
で使用した式と非常に酷似しているみたいですね。しかし、大きな違いがあります:
if
では、式は論理値を返す必要がありますが、ここでは、どんな型でも構いません。この例におけるcoin
の型は、
1行目で定義したCoin
enumです。
次は、match
アームです。一本のアームには2つの部品があります: パターンと何らかのコードです。
今回の最初のアームはCoin::Penny
という値のパターンであり、パターンと動作するコードを区別する=>
演算子が続きます。
この場合のコードは、ただの値1
です。各アームは次のアームとカンマで区切られています。
このmatch
式が実行されると、結果の値を各アームのパターンと順番に比較します。パターンに値がマッチしたら、
そのコードに紐付けられたコードが実行されます。パターンが値にマッチしなければ、コイン並べ替え装置と全く同じように、
次のアームが継続して実行されます。必要なだけパターンは存在できます: リスト6-3では、match
には4本のアームがあります。
各アームに紐付けられるコードは式であり、マッチしたアームの式の結果がmatch
式全体の戻り値になります。
典型的に、アームのコードが短い場合、波かっこは使用されません。リスト6-3では、各アームが値を返すだけなので、
これに倣っています。マッチのアームで複数行のコードを走らせたいのなら、波かっこを使用することができます。
例えば、以下のコードは、メソッドがCoin::Penny
とともに呼び出されるたびに「Lucky penny!」と表示しつつ、
ブロックの最後の値、1
を返すでしょう。
#![allow(unused)] fn main() { enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => { println!("Lucky penny!"); 1 }, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } } }
値に束縛されるパターン
マッチのアームの別の有益な機能は、パターンにマッチした値の一部に束縛できる点です。こうして、 enumの列挙子から値を取り出すことができます。
例として、enumの列挙子の一つを中にデータを保持するように変えましょう。1999年から2008年まで、
アメリカは、片側に50の州それぞれで異なるデザインをしたクォーターコインを鋳造していました。
他のコインは州のデザインがなされることはなかったので、クォーターだけがこのおまけの値を保持します。
Quarter
列挙子を変更して、UsState
値が中に保持されるようにすることでenum
にこの情報を追加でき、
それをしたのがリスト6-4のコードになります。
#![allow(unused)] fn main() { #[derive(Debug)] // すぐに州を点検できるように enum UsState { Alabama, Alaska, // ... などなど } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } }
友人の一人が50州全部のクォーターコインを収集しようとしているところを想像しましょう。コインの種類で小銭を並べ替えつつ、 友人が持っていない種類だったら、コレクションに追加できるように、各クォーターに関連した州の名前を出力します。
このコードのmatch式では、Coin::Quarter
列挙子の値にマッチするstate
という名の変数をパターンに追加します。
Coin::Quarter
がマッチすると、state
変数はそのクォーターのstateの値に束縛されます。それから、
state
をそのアームのコードで使用できます。以下のようにですね:
#![allow(unused)] fn main() { #[derive(Debug)] enum UsState { Alabama, Alaska, } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("State quarter from {:?}!", state); 25 }, } } }
value_in_cents(Coin::Quarter(UsState::Alaska))
と呼び出すつもりだったなら、coin
は
Coin::Quarter(UsState::Alaska)
になります。その値をmatchの各アームと比較すると、
Coin::Quarter(state)
に到達するまで、どれにもマッチしません。その時に、state
に束縛されるのは、
UsState::Alaska
という値です。そして、println!
式でその束縛を使用することができ、
そのため、Coin
enumの列挙子からQuarter
に対する中身のstateの値を取得できたわけです。
Option<T>
とのマッチ
前節では、Option<T>
を使用する際に、Some
ケースから中身のT
の値を取得したくなりました。要するに、
Coin
enumに対して行ったように、match
を使ってOption<T>
を扱うこともできるというわけです!
コインを比較する代わりに、Option<T>
の列挙子を比較するのですが、match
式の動作の仕方は同じままです。
Option<i32>
を取る関数を書きたくなったとし、中に値があったら、その値に1を足すことにしましょう。
中に値がなければ、関数はNone
値を返し、何も処理を試みるべきではありません。
match
のおかげで、この関数は大変書きやすく、リスト6-5のような見た目になります。
#![allow(unused)] fn main() { fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None); }
plus_one
の最初の実行についてもっと詳しく検証しましょう。plus_one(five)
と呼び出した時、
plus_one
の本体の変数x
はSome(5)
になります。そして、これをマッチの各アームと比較します。
None => None,
Some(5)
という値は、None
というパターンにはマッチしませんので、次のアームに処理が移ります。
Some(i) => Some(i + 1),
Some(5)
はSome(i)
にマッチしますか?なんと、します!列挙子が同じです。i
はSome
に含まれる値に束縛されるので、
i
は値5
になります。それから、このマッチのアームのコードが実行されるので、i
の値に1を足し、
合計の6
を中身にした新しいSome
値を生成します。
さて、x
がNone
になるリスト6-5の2回目のplus_one
の呼び出しを考えましょう。match
に入り、
最初のアームと比較します。
None => None,
マッチします!足し算する値がないので、プログラムは停止し、=>
の右辺にあるNone
値が返ります。
最初のアームがマッチしたため、他のアームは比較されません。
match
とenumの組み合わせは、多くの場面で有効です。Rustコードにおいて、このパターンはよく見かけるでしょう:
enumに対しmatch
し、内部のデータに変数を束縛させ、それに基づいたコードを実行します。最初はちょっと巧妙ですが、
一旦慣れてしまえば、全ての言語にあってほしいと願うことになるでしょう。一貫してユーザのお気に入りなのです。
マッチは包括的
もう一つ議論する必要のあるmatch
の観点があります。一点バグがありコンパイルできないこんなバージョンのplus_one
関数を考えてください:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}
None
の場合を扱っていないため、このコードはバグを生みます。幸い、コンパイラが捕捉できるバグです。
このコードのコンパイルを試みると、こんなエラーが出ます:
error[E0004]: non-exhaustive patterns: `None` not covered
(エラー: 包括的でないパターン: `None`がカバーされてません)
-->
|
6 | match x {
| ^ pattern `None` not covered
全可能性を網羅していないことをコンパイラは検知しています。もっと言えば、どのパターンを忘れているかさえ知っているのです。
Rustにおけるマッチは、包括的です: 全てのあらゆる可能性を網羅し尽くさなければ、コードは有効にならないのです。
特にOption<T>
の場合には、私達が明示的にNone
の場合を処理するのを忘れないようにしてくれます。
nullになるかもしれないのに値があると思い込まないよう、すなわち前に議論した10億ドルの失敗を犯さないよう、
コンパイラが保護してくれるわけです。
_
というプレースホルダー
Rustには、全ての可能性を列挙したくない時に使用できるパターンもあります。例えば、u8
は、有効な値として、
0から255までを取ります。1、3、5、7の値にだけ興味があったら、0、2、4、6、8、9と255までの数値を列挙する必要に迫られたくはないです。
幸運なことに、する必要はありません: 代わりに特別なパターンの_
を使用できます:
#![allow(unused)] fn main() { let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), } }
_
というパターンは、どんな値にもマッチします。他のアームの後に記述することで、_
は、
それまでに指定されていない全ての可能性にマッチします。()
は、ただのユニット値なので、_
の場合には、
何も起こりません。結果として、_
プレースホルダーの前に列挙していない可能性全てに対しては、
何もしたくないと言えるわけです。
ですが、一つのケースにしか興味がないような場面では、match
式はちょっと長ったらしすぎます。
このような場面用に、Rustには、if let
が用意されています。