Rustのmatchを完全理解!基本構文から使い方まで初心者向けに解説

2025年4月21日月曜日

Rust

matchはRustを学び始めると必ず出会う、ちょっと変わった、でもめちゃくちゃ便利な制御構文です。

他の言語のif文やswitch文に慣れていると、「なんだこりゃ?」って思うかもしれませんね。

この記事では、Rustのmatch式の基本から、ちょっと応用的な使い方まで、プログラミング初心者の方にも「なるほど!」と思っていただけるように、サンプルコードをたくさん使って解説していきます。

この記事で学べること

  • matchがどんなものか、基本的な考え方
  • matchの書き方のルール(構文)
  • 便利なパターンの使い方(変数束縛、ワイルドカード、ORパターン)
  • Rustでよく使うOption型やResult型をmatchで扱う方法
  • matchを使うときに気をつけるポイント

Rustのmatchとは?他の言語の条件分岐との違い

さて、まずは「matchって何なのさ?」という疑問から解決しましょう!

一言でいうと、matchは「値と『パターン』を比較して、一致した『アーム』の処理を実行する」ための仕組みです。

他のプログラミング言語、例えばC言語やJava、Pythonなんかには、条件によって処理を分けるためにif/else文やswitch/case文がありますよね。

Rustのmatchは、それらに似ているようで、実はもっとパワフルなんです。

何が違うかというと、一番大きな特徴は「網羅性チェック」があること。

これは、「考えられる全てのパターンをちゃんと処理するように!」とRustコンパイラが見張ってくれている機能です。もし書き漏れがあったら、コンパイル時に「おい、ここ処理書いてないぞ!」って教えてくれる親切設計。これにより、うっかりミスによるバグを防ぎやすくなるんですよ。

例えば、信号機の色を判定する場合、

  • 赤の場合
  • 黄の場合
  • 青の場合

この3パターンを全部書かないと、コンパイラに怒られちゃう、というイメージです。
この「全部の可能性を考えなさい」というルールが、プログラムの安全性を高めているんですね。

Rustのmatchの基本的な書き方(構文)

matchの正体がなんとなく分かったところで、次は実際の書き方を見ていきましょう。
基本の形はこんな感じです。

match 値 {
    パターン1 => 式1,
    パターン2 => 式2,
    パターン3 => 式3,
    // ... 他のパターンと式 ...
    _ => 式N, // ワイルドカードパターン (後で説明します)
}

分解してみましょう。

  • match: 「これからmatch式を始めますよー」という合図のキーワード。
  • : 比較したい変数やリテラル(具体的な数字や文字列など)を書きます。
  • { ... }: 波括弧の中に、条件分岐のルール(アーム)を書いていきます。
  • パターン => 式,: 「アーム」と呼ばれます。左側の「パターン」に「値」が一致したら、右側の「式」が実行されます。アームの最後はカンマ,で区切ります(最後のアームのカンマは省略してもOK)。
  • =>: パターンと実行する式を結びつける矢印みたいな記号です。「ファットアロー」なんて呼ばれたりも。

最初はちょっと見慣れないかもしれませんが、大丈夫!すぐに慣れますよ。

シンプルな値とのマッチング

まずは、一番簡単な例から見てみましょう。数字のマッチングです。// --- ソースコード ---

fn main() {
    let number = 3;

    match number {
        1 => println!("数字は1です!"),
        2 => println!("数字は2だね。"),
        3 => println!("おっ、数字は3でした!"), // ここがマッチするはず!
        _ => println!("1, 2, 3以外の数字だよ。"), // それ以外の場合
    }
}
// --- ソースコードの表示結果 ---
おっ、数字は3でした!

上のコードでは、変数numberの値(ここでは3)をmatchで調べています。
matchは上から順番にパターンをチェックしていき…

  1. 1number(3) は違う。
  2. 2number(3) も違う。
  3. 3number(3) は… 一致!

というわけで、3 => println!("おっ、数字は3でした!"), のアームが実行され、コンソールにメッセージが表示されました。

最後の _ => ... は、どのパターンにも一致しなかった場合に実行される特別なアームです。これについては、後ほど詳しく説明しますね。

matchで使える様々なパターン入門

matchのすごいところは、ただ単に「値が同じかどうか」を比べるだけじゃない点にあります。

もっと複雑な「パターン」を使って、より柔軟な条件分岐ができるんです。ここからは、初心者の方がまず押さえておきたい基本的なパターンをいくつか紹介しますね!

値を変数に束縛する

パターンに一致した時、その値を一時的な変数に入れて、後の処理で使いたい場合がありますよね。
そんな時に使うのが「変数束縛」です。

パターンの中に変数名を書くだけで、マッチした値がその変数に代入(束縛)されます。

fn main() {
    let value = Some(10); // Option型 (後で説明します)

    match value {
        Some(x) => { // Someの中の値がxに束縛される
            println!("値が見つかりました!中身は {} です。", x); // 束縛した変数xを使える
        },
        None => {
            println!("値はありませんでした。");
        },
    }
}
// --- ソースコードの表示結果 ---
値が見つかりました!中身は 10 です。

上の例では、valueSome(10)という値を持っています。
match式のSome(x)というパターンに注目してください。
valueSome(...)の形にマッチしたので、その中身である10が変数xに束縛されました。
そして、=>の右側の処理で、その変数xを使ってメッセージを表示しています。

このように、マッチした値を変数に入れて後で使えるのが変数束縛の便利な点です。

全ての可能性を拾う - ワイルドカード _

先ほどの例にも出てきましたが、_(アンダースコア)は特別なパターンで、「どんな値にもマッチする」という意味を持ちます。
これは「ワイルドカード」と呼ばれます。

match式では、考えられる全てのパターンを網羅しないとコンパイルエラーになるんでしたね。
でも、「特定のいくつかのケース以外は、全部同じ処理でいいや」という場合も多いです。

そんな時に_が大活躍!
_をmatch式の最後に書いておけば、「上記以外の全てのパターン」を拾ってくれるので、網羅性チェックをクリアできるのです。

fn main() {
    let number = 5;

    match number {
        1 => println!("いち!"),
        2 => println!("に!"),
        // 3や4のパターンは書いていないけど...
        _ => println!("3以上、または1,2以外の数字だね!"), // 1, 2以外は全部ここにマッチ
    }
}
// --- ソースコードの表示結果 ---
3以上、または1,2以外の数字だね!

この例では、numberが5なので、1にも2にもマッチしません。
そこで、最後の_パターンが「それ以外全部」をキャッチし、対応するメッセージが表示されました。
もし_パターンがないと、「パターン3, 4, 5, ... がカバーされてないよ!」とコンパイラに怒られてしまいます。

ワイルドカード_は、網羅性を満たすための必須テクニックと言えるでしょう。

複数のパターンを一度に - ORパターン |

「このパターンか、あるいは、あのパターンの場合に、同じ処理をしたい」
そんな時には、|(縦棒、パイプ記号)を使って複数のパターンを繋げられます。これをORパターンと呼びます。

fn main() {
    let character = 'k';

    match character {
        'a' | 'i' | 'u' | 'e' | 'o' => { // 'a' または 'i' または ...
            println!("{} は母音だね。", character);
        },
        'k' | 's' | 't' | 'n' | 'h' => { // 'k' または 's' または ...
             println!("{} は子音っぽい!", character); // ここにマッチ!
        },
        _ => {
            println!("{} はアルファベットの母音でも子音でもないみたい。", character);
        },
    }
}
// --- ソースコードの表示結果 ---
k は子音っぽい!

この例では、character'k'なので、'k' | 's' | 't' | 'n' | 'h' のパターンにマッチし、対応するメッセージが表示されました。
|を使えば、同じ処理をまとめられるので、コードがスッキリしますね!

Rustのmatchの実践的な使い方 - Option<T>Result<T, E>

さて、ここからはmatchが特に輝く場面、Rustプログラミングで避けては通れないOption型とResult型の扱い方を見ていきましょう。

「値があるかもしれないし、ないかもしれない」「処理が成功したかもしれないし、失敗したかもしれない」

そんな状況を安全に扱うための仕組みで、matchとの相性が抜群なんです!

Option<T>型の値を安全に取り出す

Option<T>型は、値が存在する可能性(Some(値))と、存在しない可能性(None)のどちらかを表す型です。

例えば、配列から要素を探す関数は、見つかればSome(要素)を、見つからなければNoneを返す、といった具合に使われます。

Option型の値を安全に扱うには、matchを使ってSomeの場合とNoneの場合の両方をきちんと処理するのが定石です。

fn find_first_even(numbers: Vec<i32>) -> Option<i32> {
    for num in numbers {
        if num % 2 == 0 {
            return Some(num); // 最初に見つかった偶数をSomeで包んで返す
        }
    }
    None // 見つからなければNoneを返す
}

fn main() {
    let nums1 = vec![1, 3, 5, 6, 7];
    let nums2 = vec![1, 3, 5, 7, 9];

    let first_even1 = find_first_even(nums1);
    match first_even1 {
        Some(n) => println!("nums1 で最初の偶数発見!それは {} です。", n), // Some(6) にマッチ
        None => println!("nums1 には偶数がありませんでした。"),
    }

    let first_even2 = find_first_even(nums2);
    match first_even2 {
        Some(n) => println!("nums2 で最初の偶数発見!それは {} です。", n),
        None => println!("nums2 には偶数がありませんでした。"), // None にマッチ
    }
}
// --- ソースコードの表示結果 ---
nums1 で最初の偶数発見!それは 6 です。
nums2 には偶数がありませんでした。

find_first_even関数は、整数のベクタ(配列みたいなもの)を受け取り、最初の偶数を見つけたらSome(偶数)を、見つけられなかったらNoneを返します。

main関数では、それぞれの結果をmatchで判定しています。
Some(n)パターンで値を変数nに束縛して利用したり、Noneパターンで値がなかった場合の処理をしたりしていますね。

Optionとmatchの組み合わせは、値がないかもしれない状況を安全に取り扱うための基本パターンです。

Result<T, E>型で成功と失敗を処理する

Result<T, E>型は、処理が成功した場合(Ok(成功時の値))と、失敗した場合(Err(失敗時のエラー情報))のどちらかを表す型です。

ファイルを開く、ネットワーク通信をするなど、失敗する可能性のある操作の多くがResult型を返します。

これもOptionと同様に、matchを使ってOkの場合とErrの場合を分けて処理するのが一般的です。

// 簡単な割り算関数 (0除算はエラーとする)
fn safe_divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("ゼロで割ることはできません!".to_string()) // Errでエラーメッセージを返す
    } else {
        Ok(numerator / denominator) // Okで計算結果を返す
    }
}

fn main() {
    let result1 = safe_divide(10.0, 2.0); // 成功するはず
    match result1 {
        Ok(value) => println!("割り算成功! 結果: {}", value), // Ok(5.0) にマッチ
        Err(e) => println!("割り算失敗… エラー: {}", e),
    }

    let result2 = safe_divide(5.0, 0.0); // 失敗するはず (ゼロ除算)
    match result2 {
        Ok(value) => println!("割り算成功! 結果: {}", value),
        Err(e) => println!("割り算失敗… エラー: {}", e), // Err("ゼロで割ることはできません!") にマッチ
    }
}
// --- ソースコードの表示結果 ---
割り算成功! 結果: 5
割り算失敗… エラー: ゼロで割ることはできません!

safe_divide関数は、割り算の結果をResultで返します。ゼロ除算の場合はErrを、そうでなければOkを返します。
main関数では、それぞれのResultmatchで受け取り、成功時(Ok(value))と失敗時(Err(e))で処理を分岐させていますね。

Resultとmatchの組み合わせは、エラーハンドリングの基本形であり、堅牢なプログラムを作る上で欠かせません。

Rustのmatchを使う上での注意点

matchは非常に便利ですが、いくつか気をつけておきたい点もあります。

一番のポイントは、やはり「網羅性」です。
matchで調べる値が取りうる全ての可能性に対して、パターンを用意する必要があります。
例えば、bool型(truefalseのどちらか)の値をmatchにかけるなら、

match is_active {
    true => ...,
    false => ...,
}

のように、trueの場合とfalseの場合、両方のアームを書く必要があります。(_を使っても網羅できますが、boolなら普通両方書きますね)

もし書き忘れると、コンパイラが「このパターン抜けてるよ!」と教えてくれるので、実行時エラーを防ぐ手助けになります。面倒に感じるかもしれませんが、プログラムの安全性を高めるための、Rustの優しさだと思ってください!

もう一つ、matchは「式」であるという点も覚えておくと良いでしょう。
式ということは、値を返すことができるということです。例えば、matchの結果を変数に入れることもできます。

let message = match status_code {
    200 => "成功しました!",
    404 => "見つかりません。",
    _ => "その他のステータスです。",
};
println!("{}", message); // message変数にmatchの結果が入る

各アームの=>の右側にある式が返す値の型は、全て同じ(または互換性のある)型である必要があります。

【まとめ】matchでRustらしいコードを書こう

お疲れ様でした!Rustのmatchについて、基本的なところから実践的な使い方まで見てきました。

ポイントを振り返ってみましょう。

  • matchは値とパターンを比較して処理を分岐する仕組み。
  • 網羅性チェックがあるので、全ての可能性を考慮する必要がある(_が役立つ)。
  • パターンマッチングで、値の取り出し(変数束縛)や複数条件の指定(ORパターン)ができる。
  • Option型やResult型を安全に扱うのにmatchは最適。
  • matchは式なので、値を返すことができる。

最初は少し戸惑うかもしれませんが、matchを使いこなせるようになると、Rustのコードがぐっと読みやすく、そして安全になります。まさにRustらしさを体現する機能の一つと言えるでしょう。

ぜひ、ご自身のコードでも積極的にmatchを使ってみてください。

【関連記事】
Rustとは?いま学ぶべき理由と特徴や始め方を詳しく解説

このブログを検索

  • ()

自己紹介

自分の写真
リモートワークでエンジニア兼Webディレクターとして活動しています。プログラミングやAIなど、日々の業務や学びの中で得た知識や気づきをわかりやすく発信し、これからITスキルを身につけたい人にも役立つ情報をお届けします。 note → https://note.com/yurufuri X → https://x.com/mnao111

QooQ