Rustのmatch制御フロー構造
Rustのmatch式をマスターする:網羅的なパターンマッチング、Enumデータ束縛、Optionの扱い、包括パターン。
Rustには、非常に強力な制御フロー構造である match が用意されています。値を一連のパターンと比較し、最初に一致したパターンに基づいて対応するコードを実行します。
10歳でもわかる説明(ELI5):コイン選別機#
match は硬貨を選別する機械のようなものです:
- 上部のスロットから硬貨を投入します。
- 硬貨は異なる大きさの穴が空いたレールを転がり落ちます。
- サイズがぴったり合う最初の穴を見つけると、そのケースの中に落ちます。
- それぞれの硬貨は、落ちたケースに紐づくアクションのみを実行します。
graph TD
Input["入力値 (Input Value)"] --> Arm1{"パターン1に一致?"}
Arm1 -- はい --> Action1["アーム1を実行して結果を返す"]
Arm1 -- いいえ --> Arm2{"パターン2に一致?"}
Arm2 -- はい --> Action2["アーム2を実行して結果を返す"]
Arm2 -- いいえ --> ArmCatch{"包括パターン (_ / other) に一致?"}
ArmCatch -- はい --> ActionCatch["デフォルトのアクションを実行"]
Rustでは、コンパイラがさらに安全性を保証します。あり得るすべてのパターンを網羅(Exhaustiveness) していなければコンパイルエラーになるため、予期せぬ入力によるクラッシュを防止できます。
match の基本構文#
match 式は複数の マッチアーム(match arms) で構成されます。アームは「マッチさせるパターン」と「実行するコード」の2つの部分から成り、=> 演算子で区切られます。
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("ラッキーペニー!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
fn main() {
let coin = Coin::Penny;
println!("価値: {} セント", value_in_cents(coin));
}rust[!NOTE]
matchは式(expression)であるため、値を返します。すべてのアームが返す値の型は同一でなければなりません。
値に束縛されるパターン (Patterns that Bind to Values)#
マッチアームの大きな特徴の1つは、Enumのバリアントに格納された内部データを取り出して変数に束縛(デストラクト)できる点です:
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
California,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState), // データを保持するバリアント
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("{state:?} 州のクォーター硬貨です!");
25
}
}
}
fn main() {
let quarter = Coin::Quarter(UsState::Alaska);
println!("価値: {} セント", value_in_cents(quarter));
}rustCoin::Quarter(UsState::Alaska) が渡されると、変数 state に UsState::Alaska が自動的に束縛されます。
Option<T> とのマッチング#
Rustには null が存在せず、代わりに Option<T>(Some(T) または None)が用いられます。match を使うことで、Option から安全に値を取り出して処理できます:
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("five + 1 = {six:?}");
println!("none + 1 = {none:?}");
}rustマッチの網羅性と包括パターン (Catch-all)#
Rustのマッチは網羅的である必要があります。バリアントの処理を1つでも忘れるとコンパイルエラーになります:
// コンパイルエラー: `None` パターンがカバーされていません!
fn plus_one_invalid(x: Option<i32>) -> Option<i32> {
match x {
Some(i) => Some(i + 1),
}
}rust包括変数とプレースホルダー _#
特定のパターンだけを処理し、残りのケースすべてにデフォルトの処理を適用したい場合:
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other), // その他の値を 'other' 変数に束縛して利用
}
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(), // 値を使用せず、その他のすべての値にマッチ
}
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => (), // 何もしない(ユニット値を返す)
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(_num: u8) {}
fn reroll() {}rust[!TIP] 包括パターン(
otherや_)は常にmatchの最後に配置する必要があります(上から順に評価されるため)。
まとめ#
matchは値をパターンと順次比較し、最初に一致したアームを実行します。- Enumのバリアント内に含まれるデータを取り出して変数に束縛できます。
- コンパイラが全ケースの網羅を強制するため、漏れのない安全なコードが書けます。
- 残りのケースをまとめて扱うときは、末尾に
_または包括変数を指定します。