Rustプログラミング言語において、繰り返し処理とパターンマッチングを組み合わせたwhile let構文は、コードの簡潔さと安全性を両立させる強力なツールです。

Rustでは列挙型(Enum)の値を扱う機会が多く、特にOptionResultといった型をループ内で処理する際にwhile letが非常に役立ちます。

冗長になりがちなmatch式によるループ処理を、この構文を使うことでスッキリと記述できるようになります。

この記事では、while letの基本的な使い方から、match式との具体的な違い、さらには実務で使える書き換えパターンまでを詳しく解説します。

初心者の方から、より洗練されたRustコードを書きたい中級者の方まで、幅広く役立つ内容となっています。

while letの基本概念と構文

while letは、指定したパターンが条件式の結果と一致している間、ループを継続する構文です。

基本となる構文は、while let パターン = 式 { ... }という形式で記述します。

この構文の最大の特徴は、値の取り出し(デストラクト)と条件分岐を同時に行える点にあります。

例えば、ベクタから要素を順番に取り出して処理する場合、pop()メソッドが返すOption<T>を直接判定できます。

通常であればloopmatchを組み合わせる必要がある処理も、while letなら数行で完結します。

まずは、最も標準的なwhile letのコード例を見てみましょう。

Rust
fn main() {
    // 処理対象となる数値のリストを作成
    let mut stack = vec![1, 2, 3, 4, 5];

    // stack.pop() が Some(value) を返す間、ループを継続する
    while let Some(top) = stack.pop() {
        println!("取り出した値: {}", top);
    }

    println!("すべての要素を処理しました。");
}
実行結果
取り出した値: 5
取り出した値: 4
取り出した値: 3
取り出した値: 2
取り出した値: 1
すべての要素を処理しました。

この例では、stack.pop()の結果がSomeである限り、変数topに値が束縛され、ブロック内の処理が実行されます。

値がNoneになった時点で、条件が一致しなくなり、ループは自動的に終了します。

構文のメリット

while letを使用する最大のメリットは、ボイラープレートコードを大幅に削減できることです。

Rustの型システムは厳格であるため、値が存在しない可能性(None)を常に考慮しなければなりません。

while letは、その厳格さを維持したまま、コードの見た目をクリーンに保つ役割を果たします。

match式との違いと使い分けのポイント

while letは内部的にはmatch式を簡略化したものとして扱われますが、両者には明確な使い分けの基準が存在します。

最も大きな違いは、「関心の対象が単一のパターンか、それとも全てのパターンか」という点です。

match式は、全ての可能性(網羅性)をチェックすることを強制しますが、while letは特定のパターンだけに焦点を当てます。

match式によるループの記述

比較のために、先ほどのベクタの処理をmatch式で記述してみましょう。

Rust
fn main() {
    let mut stack = vec![1, 2, 3];

    loop {
        match stack.pop() {
            Some(value) => {
                println!("値: {}", value);
            }
            // None の場合を明示的にハンドルして break する必要がある
            None => {
                break;
            }
        }
    }
}

このように、match式を使用すると、Noneの場合にループを抜ける処理を明示的に書かなければなりません。

単一のケースのみに興味がある場合、None => breakという記述は冗長に感じられるはずです。

使い分けの判断基準

以下の表は、matchwhile letのどちらを選択すべきかの目安をまとめたものです。

特徴while letloop + match
コードの簡潔さ非常に高い低い(冗長になりやすい)
網羅性の確認行わない(特定パターンのみ)強制される(すべてのケースが必要)
エラーハンドリング失敗時にループを抜けるのみ失敗時の詳細な処理が可能
主な用途イテレータやスタックの消費複雑な状態遷移や分岐

基本的には、「特定のパターンが続く限り続けたい」ならwhile letを選択しましょう。

一方で、「エラーが発生した場合はログを出し、特定の異常値ならリトライする」といった複雑な制御が必要な場合は、match式の方が適しています。

while letの実践的な書き換えパターン

実際の開発現場でよく見かける、while letへの書き換えが効果的なパターンをいくつか紹介します。

1. Iterator::next() を使った手動イテレーション

通常、イテレートにはforループを使用しますが、状況によってはnext()メソッドを直接呼び出したい場合があります。

例えば、条件に応じて一度に複数の要素を読み飛ばしたい場合などが該当します。

Rust
fn main() {
    let mut numbers = vec![10, 20, 30].into_iter();

    // 手動で next() を呼び出し、値がある限り繰り返す
    while let Some(num) = numbers.next() {
        println!("現在の数値: {}", num);
    }
}

このパターンは、ネットワークストリームからデータを読み込む際や、トークナイザの実装などで頻繁に利用されます。

2. チャンネル(mpsc)からの受信

Rustの標準ライブラリにあるマルチプロデューサー・シングルコンシューマー(mpsc)チャンネルを使用する際も、while letは活躍します。

Rust
use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let vals = vec!["Hello", "from", "thread"];
        for val in vals {
            tx.send(val).unwrap();
        }
    });

    // チャンネルが閉じられるまでメッセージを受信し続ける
    while let Ok(received) = rx.recv() {
        println!("受信メッセージ: {}", received);
    }
}

チャンネルがドロップされるとrecv()Errを返すため、「Okである間だけ受信を続ける」というロジックがシンプルに表現できます。

よくある活用シーンと高度な使い方

while letは単純なSomeOkの判定以外にも、列挙型のバリアントに応じた複雑なパターンマッチングが可能です。

独自Enumとの組み合わせ

自分で定義したカスタムEnumの状態変化を監視するループなどにも利用できます。

Rust
enum JobStatus {
    Queued(i32),
    Processing(i32),
    Completed,
}

fn main() {
    let mut jobs = vec![
        JobStatus::Queued(101),
        JobStatus::Processing(101),
        JobStatus::Completed,
    ].into_iter();

    // JobStatus::Queued の場合のみ特定の処理を行いたいとき
    // 実際には全てのパターンを処理することが多いですが、フィルタリング的に使うことも可能
    while let Some(JobStatus::Queued(id)) = jobs.next() {
        println!("待機中のジョブID: {}", id);
    }
}

この場合、最初にQueued以外のバリアントが出現した時点でループが終了することに注意してください。

もし全ての要素をチェックしたいのであれば、while letではなくforループ内でif letを使用するのが適切です。

mutableな参照を伴う場合

ループ内で値を書き換える必要がある場合、while letでもミュータブルな束縛を行うことができます。

Rust
fn main() {
    let mut data = Some(0);

    // Some(n) の n をミュータブルな変数として取り出す
    while let Some(ref mut n) = data {
        if *n >= 5 {
            println!("終了条件に達しました: {}", n);
            data = None;
        } else {
            println!("現在のカウント: {}", n);
            *n += 1;
        }
    }
}

このように、参照を意味するref mutを用いることで、ループの条件となっている対象自体を操作する高度なロジックも記述可能です。

注意点とアンチパターン

非常に便利なwhile letですが、使用する際に気をつけるべき落とし穴も存在します。

無限ループのリスク

while letの条件式で評価される変数が、ループブロック内で適切に更新されない場合、意図しない無限ループに陥る可能性があります。

特に、外部のステートを確認し続けるような処理を書く際は、必ず「いつかパターンが不一致になる状態」へ遷移することを確認してください。

可読性の低下

パターンの記述が複雑になりすぎる場合、while letを使うとかえって可読性が下がることがあります。

例えば、ネストしたタプルや複数の条件を組み合わせたパターンを一行に詰め込むと、他の開発者が意図を理解しづらくなります。

そのような場合は、素直にmatch式を使うか、一度変数に切り出すことを検討してください。

エラーの無視

while let Ok(val) = result という書き方は、Errが発生した際に何も通知せずにループを終了してしまいます。

これは、サイレントな失敗を引き起こす原因となり得ます。

エラーログを記録する必要がある重要な処理では、while letではなく、エラーハンドリングを明示的に行うloop + matchを選択すべきです。

まとめ

Rustのwhile letは、特定のパターンに合致する間だけ処理を繰り返すという、非常に簡潔で強力な構文です。

match式による冗長な記述を避け、コードの意図を明確にするために積極的に活用しましょう。

特にOptionを返すメソッドや、ストリーム的なデータの処理においてその真価を発揮します。

一方で、網羅性のチェックが行われない点や、エラーを黙殺してしまうリスクがあることも忘れてはいけません。

「簡潔さ」と「安全性」のバランスを考慮し、状況に応じて最適な構文を選択することが、優れたRustエンジニアへの近道です。

今回紹介した書き換えパターンや注意点を参考に、ぜひ日々のコーディングにwhile letを取り入れてみてください。