この記事はRust+SvelteKit+CDK で RSS 要約アプリを作ってみる Advent Calendar 2025の 22 日目の記事になります。
また、筆者が属している株式会社野村総合研究所のアドベントカレンダーもあるので、ぜひ購読ください。
パターンマッチングの仕組みがお気に入り
今回は個人開発で触ってみたい技術を使って Web アプリを作ってみました。Lambda でバックエンドを書くときには普段使っていない Rust を採用しました。書くのは初めてではないものの、去年のアドベントカレンダーのときに触って以来だったので、忘れていることも多かったです。普段の業務では TypeScript を触っているのですが、特に TS と比較してみると「パターンマッチング」が便利だと感じました。まだ使いこなせている自覚はないのですが非常に便利そうな感触を得たので、自分のためにもまとめてみようと思います。
値を返すif
パターンマッチングの前に、ifの便利さにも触れておきます。
Rust のifは「式」して扱われるので、何かしらの値を返すことができます。いわゆる TS でいう参考演算子に近いものですが、ブロックで書くことができるので、より複雑なロジックを入れることができます。
TS だと、まず変数をletで宣言し、if-elseのそれぞれのブロックで代入する、という書き方を取ることが一般的です。
let message: string;
if (score > 80) {
message = "Great job!";
} else {
message = "Keep trying!";
}
一方、Rust では変数の代入式の右辺にif-elseを持ってくることができるので、それぞれのブロックで返した値をそのまま変数に代入できます。
let message = if score > 80 {
"Great job!"
} else {
"Keep trying!"
};
パターンマッチング
Rust にはパターンマッチングの書き方がいくつかあります。
match
matchを使うと、ある変数について網羅的にパターンマッチングを行うことができます。マッチングする変数がnumberであれば範囲などで指定することもできますし、列挙型や構造体を受け取ることも可能です。その場合、デストラクトといって、変数の値を分解してそれぞれの値を扱うことができます。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
let msg = Message::Move { x: 10, y: 20 };
match msg {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Text: {}", text),
}
matchの素晴らしい点は、網羅的にパターンマッチングできているかコンパイラがチェックする点と、これも式として扱うことができる点です。すなわち、match単体で一つの値を表現することが可能です。
let input = "yes";
let confirmed = match input {
"yes" | "y" => true,
_ => false,
};
if let
また、if letを使ったパターンマッチングも可能です。これは式として扱うことはできませんが、例えば対象の値に対して 1 パターンだけマッチングしたいときに簡潔に書くことが可能です。特にデストラクトと組み合わせるのが一般的です。
let config = Some(3);
if let Some(max) = config {
println!("The maximum is configured to be {}", max);
}
let else
if letでデストラクトした場合、抽出した値は{}の中でのみ有効になります。構文が「代入できた場合、次の処理を実行する」という意味を持つためです。この場合、「代入できた場合に処理を続行し、そうでない場合は処理を中断する」というユースケースのときに使いにくくなってしまいます。なぜなら、ハッピーパス(処理に成功してプログラムが続行する処理の流れ)を書こうとするとネストが深くなってしまうためです。
fn get_count(s: &str) -> u64 {
let mut it = s.split(' ');
// ネスト(深さ)が深くなってしまう
if let Some(count_str) = it.next() {
if let Ok(count) = count_str.parse::<u64>() {
return count;
}
}
panic!("パースに失敗しました");
}
これに対応するために、let elseという構文があります。これは、「代入できなければ次の処理を実行する」という意味を持ちます。すなわち、早期リターンのように、デストラクトしつつそれができなければ処理を中断する、ということが書けるようになります。
fn get_count(s: &str) -> u64 {
let mut it = s.split(' ');
// 1. matchしなかったら即座にpanic!して終了
let Some(count_str) = it.next() else {
panic!("要素がありません");
};
// 2. パースに失敗したら即座にpanic!して終了
let Ok(count) = count_str.parse::<u64>() else {
panic!("数字ではありません");
};
// 成功した結果(count)を、ネストさせずにそのまま返せる
count
}
let elseの{}内は diverge(発散)する必要があります。このブロックは次の処理に進んではいけないので、必ず
breakreturnpanic!
で終わる必要があります。
while let
while文にもlet構文が使えます。デストラクトできる限りループを続行します。
let mut stack = vec![1, 2, 3];
// pop() が Some(x) を返す間だけループが続く
while let Some(x) = stack.pop() {
println!("{}", x);
}
// None が返ってきた時点で、自動的にループを抜ける
なぜmatch以外にたくさんあるのか?
matchの利点は網羅的にパターンマッチングを行える点ですが、逆に言うとすべてのパターンについて分岐を定義しなければなりません。例えば成功時にのみ続きの処理を書きたいとしても、そうでない場合も定義しておかないとmatchはコンパイルエラーになってしまいます。そのため、if letやwhile letのような構文を活用するとより簡潔に書くことができます。