Rustのif letによる簡潔な制御フロー
Rustのif letとif let elseをマスターする:単一のパターンにマッチする場合のコードを簡潔にし、matchの冗長さを削減。
match は安全で強力な網羅性チェックを提供しますが、特定の1つのパターンのみに関心があり、他のすべてのケースを無視したい場合があります。そのような場面で _ => () を含む match 式を書くのは、冗長なボイラープレートコードになりがちです。
Rustでは、こうしたケースをより簡潔に記述するための構文として if let が用意されています。
10歳でもわかる説明(ELI5):中身がわからないプレゼント箱#
中身がわからないプレゼント箱を想像してみてください:
- 箱の中にはおもちゃが入っているか(
Some(toy))、あるいは完全に空っぽ(None)です。 - あなたはおもちゃが入っているときだけ取り出して遊びたいと考えています。空っぽの場合は何もせずその場を離れるだけです。
graph TD
Input["入力値 (Option / Enum)"] --> Check{"指定したパターンに一致?"}
Check -- はい --> Bind["値を抽出して if let ブロックを実行"]
Check -- いいえ --> ElseCheck{"else ブロックはある?"}
ElseCheck -- はい --> ElseAction["else ブロックを実行"]
ElseCheck -- いいえ --> Ignore["無視して処理を継続"]
match と if let の比較#
設定値が Some(u8) の場合のみ値を出力したいケースを考えます:
match を使った場合(冗長)#
src/main.rs
let config_max = Some(3u8);
match config_max {
Some(max) => println!("設定された最大値は {max} です"),
_ => (), // 網羅性を満たすために必須のボイラープレート
}rustif let を使った場合(簡潔)#
src/main.rs
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("設定された最大値は {max} です");
}rust[!TIP]
if letは、1つのパターンに一致したときだけ処理を行い、それ以外を無視するようなmatch式の糖衣構文(Syntax Sugar)と考えることができます。
if let と else の組み合わせ#
if let には else ブロックを組み合わせることも可能です(これは match 式の _ => ... アームと同等の挙動になります):
src/main.rs
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
California,
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn count_coin(coin: Coin, count: &mut i32) {
if let Coin::Quarter(state) = coin {
println!("{state:?} 州のクォーター硬貨です!");
} else {
*count += 1;
}
}
fn main() {
let mut count = 0;
let coin1 = Coin::Quarter(UsState::Alaska);
let coin2 = Coin::Penny;
count_coin(coin1, &mut count);
count_coin(coin2, &mut count);
println!("25セント以外の硬貨の枚数: {count}");
}rust比較:if let と通常の if の違い#
Rust初心者がよく疑問に思うのは、「通常の if 文があるのに、なぜ if let が必要なのか?」という点です。
その決定的な違いは、パターンマッチングとデータ抽出(分配束縛) にあります:
src/main.rs
let score = Some(95);
// 1. 通常の `if` を使用:真偽値(true/false)の評価のみ
if score.is_some() {
// 内部の値を取り出すために手動で unwrap が必要 -> 冗長かつ危険
let val = score.unwrap();
println!("スコア: {val}");
}
// 2. `if let` を使用:パターンの照合と安全な変数束縛を1ステップで実行
if let Some(val) = score {
println!("スコア: {val}"); // 完全に安全。unwrap() の呼び出しは不要
}rustgraph TD
subgraph IfOrdinary ["通常の if"]
Cond["Boolean条件(true/false)を評価"] --> Unsafe["内部データ取得に手動 unwrap が必要"]
end
subgraph IfLet ["if let"]
Pattern["パターンマッチング"] --> Safe["自動的にデータを抽出し安全にローカル変数へ束縛"]
end
if、if let、match の使い分け#
| 評価軸 | 通常の if | if let | match |
|---|---|---|---|
| 条件の判定対象 | 真偽値式(bool) | 一致対象のパターン(Pattern) | あり得るすべてのパターン |
| データの抽出 | 不可(手動 unwrap が必要) | 可能(自動で値を取り出して束縛) | 可能(アームごとに安全に束縛) |
| 網羅性チェック | なし | なし(単一パターンのみ) | あり(100%全ケースの網羅を強制) |
| 分岐数 | 1 (if) または 2 (if/else) | 1 (if let) または 2 (if let/else) | 任意(複数アーム) |
| 最適な用途 | 数値の大小比較、論理フラグ判定 | Option や Enum から1つのバリアントを取り出す | 複数パターンの分岐、複雑な状態遷移 |
[!WARNING]
if letを採用すると、matchが提供するコンパイラの厳格な網羅性チェックを手放すことになります。将来的にEnumのバリアントが追加された際に処理漏れが生じないか留意してください。
まとめ#
if letは単一パターンの一致判定とデータ抽出を簡潔に書くための構文です。- 必要に応じて
elseを追加し、不一致時の処理を記述できます。 matchの冗長なボイラープレートを排除したい場合に有効です。