じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

`if let` chain — Rust 1.88 / 2024 edition で実用フェーズに入った

はじめに

Rust 1.88 (2025年) で if let chain が安定化されました。2024 edition のコードでは積極的に使えます。 &&if let 条件を連結できるようになり、ネストした if let を平らに書ける構文です。短い変更ですが、効果は大きく出ます。

何を解決するか

Option が複数あって、すべて Some だったときだけ何かしたい、という場面。素直に書くとネストになります。

fn first_premium(items: &[String]) -> Option<&str> {
    if let Some(first) = items.first() {
        if let Some(rest) = first.strip_prefix("premium-") {
            return Some(rest);
        }
    }
    None
}

「最初の要素を取り出して、それが premium- で始まっていれば残りを返す」という処理ですが、 if let が2段ネストして本筋が見えにくいです。

if let chain での書き方

2024 edition / Rust 1.88 以降ならこう書けます。

fn first_premium(items: &[String]) -> Option<&str> {
    if let Some(first) = items.first()
        && let Some(rest) = first.strip_prefix("premium-")
    {
        return Some(rest);
    }
    None
}

&&let パターンを連結します。「最初の let が成功して、かつ次の let も成功する」が満たされたときだけ if 本体に入ります。

ポイントを整理します。

  • let で取り出した変数 (first, rest) は 後続の条件と if 本体の両方で使える
  • 普通の bool 条件と混ぜられる: if let Some(x) = a && x > 0 && let Some(y) = b { ... }
  • 失敗したら全体の if は false 扱い

ネストが消えて、guard 条件が一直線に読めます。

let-else との使い分け

似た用途で let-else もあります。

fn first_premium_letelse(items: &[String]) -> Option<&str> {
    let Some(first) = items.first() else { return None; };
    let Some(rest) = first.strip_prefix("premium-") else { return None; };
    Some(rest)
}

両者は使い分けがあります。

構文 向いている場面
if let chain 「成功したらある処理、失敗したら何もしない」: 条件が満たされた時だけ実行する
let-else 「失敗したら関数を抜ける」: ガード句として使い、以降の処理を平らに続ける

first_premium の例だと、「失敗時は None を返すだけ」なので let-else のほうが少しシンプルでしょう。一方、「成功したらこの if 本体に入って、入らなければ後続処理に進む」のような continue 的な用途では if let chain が自然です。

具体例を見ます。

fn process(events: &[Event]) {
    for event in events {
        if let Event::Click { target } = event
            && let Some(url) = target.url()
            && url.starts_with("http")
        {
            println!("opening {url}");
        }
        // 失敗したら次の event に進む
    }
}

# enum Event { Click { target: Target } }
# struct Target;
# impl Target { fn url(&self) -> Option<&str> { None } }

ループの中で「3条件すべて満たしたら何かする」を1つの if で書けます。let-else でループから continue するより自然です。

bool 条件と混ぜる

if let chain は普通の bool 条件と混ぜられます。

fn handle(message: &Message, allowlist: &[String]) {
    if let Some(sender) = &message.sender
        && allowlist.contains(sender)
        && let Some(body) = &message.body
        && !body.is_empty()
    {
        println!("ok: {sender} -> {body}");
    }
}

# struct Message { sender: Option<String>, body: Option<String> }

let の取り出しと、普通の比較条件 (allowlist.contains, is_empty) が混ざっても、左から右に評価されます。途中で false なら短絡します。

何が嬉しいのか

主な3点を挙げます。

  1. ネスト削減: if let の2段3段ネストが消える
  2. 意図が線形に読める: 「これとこれが、さらにこれが揃ったら」が左から右に並ぶ
  3. 副条件を挟みやすい: let の合間に普通の bool 条件を入れられる

特に3は地味に便利で、「if let Some(x) と書いてから if x > 0 のために中で if を1個増やす」というパターンが消えます。

注意: 安定化は 1.88

if let chain が stable になったのは Rust 1.88 です。それより古い rustc ではコンパイルが通りません。Cargo.tomlrust-version = "1.88" 以上を書いていなければ、CI 用に最低でも 1.88 で動かす必要があります。

[package]
edition = "2024"
rust-version = "1.88"  # if let chain を使うなら

2024 edition そのものは 1.85 から有効ですが、if let chain のために MSRV を 1.88 に上げる、という関係になります。

簡単に試す

Cargo.toml:

[package]
name = "if-let-chain-demo"
version = "0.1.0"
edition = "2024"
rust-version = "1.88"

src/main.rs:

fn main() {
    let items = vec![
        "premium-coffee".to_string(),
        "tea".to_string(),
    ];

    if let Some(first) = items.first()
        && let Some(rest) = first.strip_prefix("premium-")
    {
        println!("found premium: {rest}");
    } else {
        println!("nothing premium");
    }
}

cargo runfound premium: coffee が出ます。

まとめ

  • if let chain は 2024 edition / Rust 1.88 で実用フェーズに入った
  • &&let パターンと bool 条件を連結できる
  • 取り出した変数は後続の条件と if 本体の両方で使える
  • let-else は「失敗したら抜ける」、if let chain は「成功したら何かする」で使い分け
  • 使うときは MSRV を 1.88 以上に揃える

関連