if let und while let sind syntaktische Abkürzungen für match mit genau einem interessanten Arm. Wenn dich nur ein Pattern interessiert — typischerweise Some(x) aus einem Option oder Ok(x) aus einem Result — ist ein vollständiges match mit einem _ => {}-Arm overkill. if let pattern = value { ... } macht das in einer Zeile. while let läuft eine Schleife, solange ein Pattern matcht. Dieser Artikel zeigt beide Konstrukte, die let chains (seit Edition 2024) und die typischen Anwendungs-Patterns.

if let — kompakter Pattern-Match

Rust if let Grundform
fn main() {
    let maybe: Option<i32> = Some(42);

    // Klassisch mit match:
    match maybe {
        Some(n) => println!("Wert: {n}"),
        None => {}      // None ist langweilig
    }

    // Kompakt mit if let:
    if let Some(n) = maybe {
        println!("Wert: {n}");
    }
}

if let pattern = value { ... } läuft den Body nur, wenn das Pattern matcht. Die im Pattern gebundenen Variablen (n) sind im Body verfügbar.

Mit else

Rust if let else
fn main() {
    let maybe: Option<i32> = Some(42);
    if let Some(n) = maybe {
        println!("Wert: {n}");
    } else {
        println!("Kein Wert");
    }
}

else läuft, wenn das Pattern nicht matcht. Wie ein zwei-armiger match, aber kürzer.

Mit else if let — Kette

Rust else if let
enum Event { Click(u32, u32), KeyDown(char), Idle }

fn behandle(e: &Event) -> String {
    if let Event::Click(x, y) = e {
        format!("Klick bei ({x}, {y})")
    } else if let Event::KeyDown(c) = e {
        format!("Taste: {c}")
    } else {
        "Idle".to_string()
    }
}

Mehrere Patterns in Kette. Bei mehr als 2-3 Varianten wird match aber meist lesbarer.

Wann if let, wann match?

SituationWahl
Nur ein Pattern interessiertif let
2+ Patterns interessierenmatch
Exhaustiveness-Garantie wichtigmatch
Wert auch im else-Branch nötigif let else oder match
Drain/Iterationwhile let

Faustregel: if let bei 1 Pattern, match ab 2 — der Compiler macht den Exhaustiveness-Check, was Bugs verhindert.

while let — Pattern-getriebene Schleife

Rust while let mit Stack
fn main() {
    let mut stack = vec![1, 2, 3, 4, 5];

    while let Some(top) = stack.pop() {
        println!("{top}");
    }
    // 5, 4, 3, 2, 1
    assert!(stack.is_empty());
}

pop() gibt Option<T> zurück — Some solange etwas da ist, None bei leerer Vec. while let läuft, solange das Pattern matcht.

Klassisches Drain-Pattern für Container.

let chains (seit Edition 2024)

Mehrere let-Patterns plus boolesche Bedingungen mit &&:

Rust let chains
struct Konfig { db: Option<String>, port: Option<u16> }

fn main() {
    let c = Konfig { db: Some("postgres://...".into()), port: Some(5432) };
    if let Some(url) = &c.db
        && let Some(p) = c.port
        && p > 1024
    {
        println!("DB={url} auf Port {p}");
    }
}

Vor Edition 2024 wären das verschachtelte if lets:

Rust Vor Edition 2024
# struct Konfig { db: Option<String>, port: Option<u16> }
# let c = Konfig { db: Some("postgres://...".into()), port: Some(5432) };
if let Some(url) = &c.db {
    if let Some(p) = c.port {
        if p > 1024 {
            println!("DB={url} auf Port {p}");
        }
    }
}

let chains machen die natürliche „und"-Logik in einer Zeile. Voraussetzung: Edition 2024 im Cargo.toml.

Mehrere Patterns mit |

if let und while let unterstützen Or-Patterns:

Rust Or-Pattern
enum Status { Ok(i32), Warning(i32), Error(String) }

fn extrahiere_zahl(s: &Status) -> Option<i32> {
    if let Status::Ok(n) | Status::Warning(n) = s {
        Some(*n)
    } else {
        None
    }
}

Status::Ok(n) | Status::Warning(n) matcht beide Varianten — n ist in beiden gebunden, beide haben den gleichen Typ.

while let mit komplexen Patterns

Rust Iterator-Drain
use std::collections::VecDeque;

fn main() {
    let mut queue: VecDeque<(u64, String)> = VecDeque::new();
    queue.push_back((1, "first".into()));
    queue.push_back((2, "second".into()));

    while let Some((id, payload)) = queue.pop_front() {
        println!("Job {id}: {payload}");
    }
}

Pattern-Destrukturierung im while let — sehr typisch für Worker-Loops mit strukturierten Items.

Praxis: if let / while let im echten Code

Option-Verarbeitung

Rust Cache-Lookup
use std::collections::HashMap;

pub fn drucke_cache_wert(cache: &HashMap<String, String>, key: &str) {
    if let Some(wert) = cache.get(key) {
        println!("Cache-Hit: {wert}");
    } else {
        println!("Cache-Miss für {key}");
    }
}

Standard-Pattern für Map-Lookups.

Result-Verarbeitung

Rust Result-Ignore
pub fn versuche_aufruf() {
    if let Ok(inhalt) = std::fs::read_to_string("/etc/hostname") {
        println!("Hostname: {}", inhalt.trim());
    }
    // Bei Err wird einfach nichts gemacht
}

Wenn der Fehler unwichtig ist und du nur den Success-Fall behandeln willst.

Drain einer Queue

Rust Drain
pub fn verarbeite_alle(queue: &mut Vec<String>) {
    while let Some(item) = queue.pop() {
        println!("Verarbeite: {item}");
    }
}

Klassisches Worker-Pattern.

Channel-Empfang

Rust Channel-Loop
use std::sync::mpsc::Receiver;

pub fn verarbeite_messages(rx: Receiver<String>) {
    while let Ok(msg) = rx.recv() {
        println!("Erhalten: {msg}");
    }
    // Schleife endet, wenn der Channel geschlossen wird (Err).
}

recv() gibt ResultOk bei Nachricht, Err bei geschlossenem Channel. Standard-Empfänger-Loop.

Konfigurations-Lookup

Rust Config
pub fn aktiviere_feature_wenn_da(config: &HashMap<String, bool>, feature: &str) {
    if let Some(&true) = config.get(feature) {
        println!("Feature {feature} aktiv");
    }
}
# use std::collections::HashMap;

Some(&true) — Pattern auf inneren Wert. Sehr kompakt.

Iterator durchlaufen mit Pattern

Rust Iter mit Pattern
pub fn drucke_zahlen(input: &[&str]) {
    for s in input {
        if let Ok(n) = s.parse::<i32>() {
            println!("{n}");
        }
        // Sonstige: ignorieren
    }
}

if let innerhalb eines Loops — filtert und transformiert in einem Schritt.

Nested Option mit Edition-2024-Chains

Rust Chained Pattern
struct Profil { name: String, telefon: Option<String> }
struct Account { profil: Option<Profil> }

pub fn drucke_telefon(a: &Account) {
    if let Some(p) = &a.profil
        && let Some(t) = &p.telefon
    {
        println!("Telefon: {t}");
    }
}

Edition 2024 macht das aus zwei verschachtelten if lets ein einziger lesbarer Block.

State-Machine-Step mit if let

Rust State-Update
enum State { Inaktiv, Aktiv(u32), Beendet }

pub fn next(state: State) -> State {
    if let State::Aktiv(counter) = state {
        if counter > 10 {
            State::Beendet
        } else {
            State::Aktiv(counter + 1)
        }
    } else {
        state
    }
}

if let mit Variable-Match plus Guard im Body.

Worker-Loop mit komplexer Bedingung

Rust Producer/Consumer
use std::collections::VecDeque;

pub fn verarbeite_jobs(jobs: &mut VecDeque<(u64, i32)>) -> i32 {
    let mut summe = 0;
    while let Some((id, wert)) = jobs.pop_front() {
        if wert < 0 {
            println!("Job {id} übersprungen (negative)");
            continue;
        }
        summe += wert;
    }
    summe
}

Drain mit Destrukturierung und Skip-Logik.

Optional-Field-Update

Rust Update wenn da
struct Settings { email: Option<String>, telefon: Option<String> }

pub fn email_normalisieren(s: &mut Settings) {
    if let Some(email) = &mut s.email {
        *email = email.trim().to_lowercase();
    }
}

if let Some(...) = &mut ... — mutable Pattern, bindet als mutable Referenz. Update direkt am Wert.

FAQ

Wann if let, wann match?

if let bei genau einem interessanten Pattern. match ab zwei. match hat Exhaustiveness-Check, der bei neuen Varianten den Compiler aufmerksam macht — if let „versteckt" andere Fälle. Bei Unsicherheit: match ist sicherer.

while let ist der idiomatische Drain-Loop.

while let Some(x) = container.pop() { ... } läuft, bis der Container leer ist. Klassisch für Worker-Queues, Stack-Verarbeitung, Iterator-Konsumieren.

let chains sind Edition 2024 stable.

if let A && let B && cond { ... } ist seit Rust 1.85 / Edition 2024 stable. Auf älteren Editionen: verschachtelte if lets.

if let mit Or-Patterns funktioniert.

if let Some(x) | NextSome(x) = val { ... } ist legal. Mehrere Patterns mit gleichem Binding-Typ. Sehr expressiv.

Mutable Pattern: if let Some(x) = &mut ....

Für In-Place-Mutation: if let Some(x) = &mut option { *x = neu; }. x ist eine mutable Referenz, durch *x = ... änderst du den inneren Wert.

Mit else wird if let wie 2-armiges match.

if let Some(n) = x { ... } else { ... } ist syntaktisch identisch zu match x { Some(n) => ..., _ => ... }. Geschmackssache.

while let mit iter_mut bricht Borrow-Checker.

Während while let Some(x) = iter.next() läuft, ist die Sammlung über iter geborgt — du kannst sie nicht parallel modifizieren. Bei pop() ist das anders, weil jeder Pop verbraucht — keine bestehende Borrow.

else if let-Ketten sind möglich, aber match ist meist klarer.

if let A = x { ... } else if let B = x { ... } else { ... } funktioniert. Bei 3+ Patterns wird match lesbarer. Faustregel: bei 2 Patterns mit unterschiedlicher Logik — if let / else if let. Bei 3+ — match.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Enums & Pattern Matching

Zur Übersicht