Rustという言語がシステムプログラミングの領域で確固たる地位を築いた大きな理由の一つに、強力な型システムと安全性があります。
その安全性を支え、かつプログラムの可読性を飛躍的に高めている機能がmatch式によるパターンマッチングです。
2026年現在、多くの開発者がRustを選択する中で、このmatch式をいかに洗練された形で記述できるかが、コードの品質を左右する重要な要素となっています。
本記事では、Rustにおけるmatch式の基本構造から、実戦で役立つ高度なテクニックまでを詳しく紹介します。
match式の基本構造と網羅性の保証
Rustのmatch式は、他の言語におけるswitch文に近い役割を持ちますが、その性質は根本的に異なります。
最も大きな違いは、match式が「式」であり、値を返すことができる点にあります。
また、Rustのコンパイラはmatch式においてパターンの網羅性 (Exhaustiveness)を厳格にチェックします。
すべての可能性が考慮されていない場合、コンパイルエラーが発生するため、実行時の予期せぬバグを未然に防ぐことが可能です。
まずは、基本的なmatch式の書き方を確認してみましょう。
// 基本的な数値のパターンマッチング
fn check_number(n: i32) {
match n {
// 単一の値にマッチさせる
1 => println!("1です"),
// 複数の値のいずれかにマッチさせる
2 | 3 | 5 | 7 => println!("小さな素数です"),
// 範囲指定でマッチさせる (10から20まで)
10..=20 => println!("10以上20以下です"),
// 上記のどれにも当てはまらない場合 (ワイルドカード)
_ => println!("その他の数値です"),
}
}
この例では、変数nの値に応じて異なるメッセージを出力しています。
最後の_はワイルドカードパターンと呼ばれ、それまでのパターンに合致しなかったすべての値を受け入れる役割を持ちます。
Rustではすべてのパターンを網羅しなければならないため、不特定の値を扱う場合にはこのワイルドカードが必須となります。
列挙型 (Enum) との組み合わせ
Rustのmatch式が真価を発揮するのは、列挙型 (Enum) と組み合わせたときです。
Enumの各バリアントが持つデータ構造を解析し、それに応じた処理を簡潔に記述できます。
特に、値が存在しない可能性を示すOption型や、エラーが発生する可能性を示すResult型の処理には欠かせません。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => {
println!("終了します");
}
Message::Move { x, y } => {
println!("座標 ({}, {}) へ移動します", x, y);
}
Message::Write(text) => {
println!("メッセージ: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("色を R:{} G:{} B:{} に変更します", r, g, b);
}
}
}
上記のコードでは、MessageというEnumの各状態に応じて異なる処理を行っています。
Message::Move { x, y }のように、構造体形式のバリアントから内部の値を直接取り出せる (デストラクト)のが大きな特徴です。
これにより、複雑なデータ構造を安全かつ効率的に操作することが可能になります。
マッチガードによる条件分岐の拡張
パターンマッチングの際に、単なる値の合致だけでなく、追加の条件を付加したい場合があります。
そのような場面で使用されるのがマッチガード (Match Guards)です。
パターンの後ろにif文を記述することで、その条件が真である場合のみマッチさせることができます。
fn check_temperature(temp: i32, is_celsius: bool) {
match temp {
// マッチガードを使用して追加条件を指定
t if t > 30 && is_celsius => println!("とても暑い日です"),
t if t < 0 => println!("氷点下です"),
_ => println!("快適な温度です"),
}
}
マッチガードを利用することで、パターンだけでは表現しきれない複雑な論理をmatch式の中に組み込むことができます。
ただし、マッチガードを使用するとコンパイラは網羅性のチェックを完全には行えなくなる場合があるため、最後に必ずワイルドカードやデフォルトの処理を用意しておくことが推奨されます。
@バインディングによる値の利用
パターンマッチングを行いながら、その値を別の変数に束縛したい場合には、@演算子を使用します。
これは、ある範囲内に値があることを確認しつつ、その値自体を後続の処理で使いたいときに非常に便利です。
enum User {
Admin { id: i32 },
Regular { id: i32 },
}
fn identify_user(user: User) {
match user {
// IDが1から10の範囲にあるAdminをキャッチし、そのidを使用する
User::Admin { id: id_val @ 1..=10 } => {
println!("初期管理者のIDは {} です", id_val);
}
User::Admin { id } => {
println!("管理者のIDは {} です", id);
}
_ => println!("一般ユーザーです"),
}
}
id_val @ 1..=10という記述により、「範囲内であるかどうかのチェック」と「変数への代入」を同時に行っています。
この機能により、コードを冗長にすることなく、必要な情報をスマートに抽出できます。
match式を記述する際のベストプラクティス
match式は非常に強力ですが、乱用するとコードのネストが深くなり、可読性を損なう恐れがあります。
そのため、適切な使い分けが重要です。
if let 文の活用
特定の1つのパターンだけに興味があり、それ以外の場合は何もしない、あるいは共通の処理をするという場合には、if let文の使用を検討してください。
match式よりも簡潔に記述でき、コードの意図が明確になります。
let some_option = Some(10);
// match式の場合
match some_option {
Some(i) => println!("値は {} です", i),
None => (),
}
// if let文の場合 (よりシンプル)
if let Some(i) = some_option {
println!("値は {} です", i);
}
複雑なパターンの共通化
複数のパターンで同じ処理を行いたい場合は、| (パイプ) を使ってパターンを結合します。
これにより、同じコードを何度も書く必要がなくなり、メンテナンス性が向上します。
match式での所有権に注意する
match式で構造体やEnumを扱う際、内部のデータが非コピー型 (Stringなど) である場合、所有権の移動 (Move)が発生します。
もし元の値を保持したままマッチングを行いたい場合は、match &valのように参照を渡すか、パターン内でrefキーワードを使用する必要があります。
2026年におけるパターンマッチングの進化
Rustの進化は続いており、パターンマッチングに関連する機能もより洗練されています。
現在では、より直感的な構文で複雑な参照のデストラクトが可能になっており、コンパイラのアドバイスも非常に親切になっています。
開発者はコンパイルエラーを恐れるのではなく、コンパイラとの対話を通じて、より堅牢なロジックを構築していく姿勢が求められます。
match式をマスターすることは、Rustの哲学である「安全で高速なコード」を実現するための第一歩と言えるでしょう。
まとめ
Rustのmatch式は、単なる条件分岐の手段を超えた、強力な制御フローツールです。
網羅性のチェックによる安全性、デストラクトによる高い表現力、そしてマッチガードや@バインディングによる柔軟性を兼ね備えています。
これらを適切に組み合わせることで、バグが少なく、かつ読みやすいコードを記述することができます。
日常的なプログラミングの中で、常に「この分岐はmatchでより安全に書けないか」と自問自答することで、Rustaceanとしてのスキルはさらに磨かれていくはずです。
今回紹介した基本ルールと実践的なテクニックを、ぜひ日々の開発に役立ててください。
