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
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
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
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?
| Situation | Wahl |
|---|---|
| Nur ein Pattern interessiert | if let |
| 2+ Patterns interessieren | match |
| Exhaustiveness-Garantie wichtig | match |
| Wert auch im else-Branch nötig | if let else oder match |
| Drain/Iteration | while let |
Faustregel: if let bei 1 Pattern, match ab 2 — der Compiler macht den Exhaustiveness-Check, was Bugs verhindert.
while let — Pattern-getriebene Schleife
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 &&:
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:
# 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:
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
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
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
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
pub fn verarbeite_alle(queue: &mut Vec<String>) {
while let Some(item) = queue.pop() {
println!("Verarbeite: {item}");
}
}Klassisches Worker-Pattern.
Channel-Empfang
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 Result — Ok bei Nachricht, Err bei geschlossenem Channel. Standard-Empfänger-Loop.
Konfigurations-Lookup
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
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
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
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
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
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
- The Rust Book – if let
- Rust Reference – if let expressions
- Rust Reference – while let
- Rust 2024 Edition – let chains