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 ist eine Pattern-Match-Form, die in eine if-Struktur eingebettet ist. Die Syntax if let pattern = value { ... } liest sich als: „wenn value zum Pattern passt, dann führe den Body aus". Die im Pattern gebundenen Variablen (hier n) sind im Body verfügbar — wie bei einem match-Arm.

Der semantische Unterschied zum vollständigen match ist, dass if let nicht exhaustive geprüft wird. Du bekommst keine Warnung, wenn das Enum weitere Varianten hat — alle nicht-passenden Werte werden stillschweigend übersprungen. Damit ist if let syntaktisch kompakter, aber semantisch weniger streng. Du wählst es bewusst, wenn dich wirklich nur ein bestimmtes Pattern interessiert.

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());
}

while let ist die Schleifen-Variante von if let: solange das Pattern matcht, läuft der Body weiter. Sobald das Pattern nicht mehr matcht, endet die Schleife. Das ist perfekt für Pattern-getriebene Iteration.

pop() auf einem Vec gibt Option<T> zurück — Some(wert) solange etwas da ist, None bei leerer Vec. Damit läuft die Schleife genau so lange, bis der Vec leer ist; danach matcht das Some(top)-Pattern nicht mehr, und die Schleife endet. Klassisches Drain-Pattern: ein Container wird vollständig geleert.

Anwendungsfälle gibt es viele: Stack-basierte Algorithmen (Tiefen-Suche, Expression-Parser), Queue-Verarbeitung (Job-Loop), Channel-Reads (while let Some(msg) = rx.recv() { ... }). Überall, wo „solange noch was kommt"-Semantik gefragt ist.

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 (auch „let chain expressions") sind eine Sprach-Erweiterung in Edition 2024, die mehrere let-Patterns plus boolesche Bedingungen in einer einzigen if-Condition kombiniert. Die Syntax if let A = a && let B = b && bedingung { ... } ist dabei kurz-circuit: ab dem ersten nicht-matchenden Pattern wird abgebrochen, ohne die folgenden zu evaluieren.

Der Unterschied zur Pre-2024-Variante ist nicht nur kosmetisch: die verschachtelten if let-Statements wachsen mit jeder Bedingung um eine Einrückungs-Ebene, was bei drei oder mehr Conditions schnell unleserlich wird. let chains halten die Logik flach und linear.

Wichtig: dies ist ein Edition-2024-Feature. Im Cargo.toml muss edition = "2024" gesetzt sein, sonst lehnt der Compiler die Syntax ab. Bei Library-Code mit weiteren Konsumenten lohnt sich zu prüfen, welche Editionen unterstützt werden müssen.

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
    }
}

Or-Patterns (|) erlauben, mehrere alternative Patterns in einem Arm zu kombinieren. Status::Ok(n) | Status::Warning(n) matcht beide Varianten, und in beiden Fällen wird die innere Zahl an n gebunden.

Voraussetzungen: alle alternativen Patterns müssen dieselben Bindungen (mit denselben Typen) deklarieren. Bei Status::Ok(n) | Status::Error(s) wäre das Compile-Fehler, weil n und s unterschiedlich heißen und nicht beide gleichzeitig gebunden werden können. Die Or-Pattern-Syntax verlangt strukturelle Konsistenz auf der Binding-Seite.

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