while ist die klassische Bedingungs-Schleife — bei jedem Durchgang wird die Bedingung am Anfang geprüft, und solange sie true ist, läuft der Body. In Rust ist while etwas unauffälliger als in anderen Sprachen: es kann keinen Wert über break zurückgeben (das ist loops Spezialität), und es konkurriert oft mit for (bei bekannten Sequenzen) und loop (bei Bedingungen, die erst im Body sichtbar werden). Doch für eine ganze Klasse von Anwendungen — Iteration über einen Generator-artigen State, Drain-Patterns, polling-with-condition — ist while exakt richtig. Plus die while let-Variante als kompaktes Pattern-Match.
Grundform
fn main() {
let mut n = 5;
while n > 0 {
println!("{n}");
n -= 1;
}
println!("Fertig!");
}Die Bedingung muss bool sein (keine truthy-Werte, siehe if-Artikel). Solange sie true ist, läuft der Body — sobald sie false wird, geht's nach der Schleife weiter.
while hat immer Rückgabetyp (). break <wert> ist syntaktisch verboten — wenn du das brauchst, nimm loop.
while let — Pattern-getriebene Iteration
Sehr nützliche Variante: solange ein Pattern matcht, weiterlaufen:
fn main() {
let mut stack = vec![1, 2, 3, 4, 5];
while let Some(top) = stack.pop() {
println!("{top}");
}
// 5, 4, 3, 2, 1
}Vec::pop gibt Option<T> zurück — Some(wert) wenn da was war, None wenn der Vec leer ist. while let Some(top) = stack.pop() läuft, solange das Pattern matcht (also solange pop einen Wert liefert).
Idiomatisch für Drain-Patterns: Container leeren, jedes Element bearbeiten.
while vs. loop vs. for
| Situation | Bevorzugte Form |
|---|---|
| Sequenz/Iterator durchlaufen | for |
| Bedingung am Anfang prüfbar, kein Wert nötig | while |
| Pattern matcht (Option/Result drainen) | while let |
| Bedingung erst im Body, oder Wert nötig | loop mit break |
Faustregel: wenn die Schleifen-Bedingung am Anfang einfach formulierbar ist und kein Wert zurückgegeben werden muss, ist while die natürliche Wahl. Für alles andere gibt es bessere Alternativen.
Klassische Stolperfallen
Borrow-Konflikt mit while let
Beim Iterieren über eine &mut-Methode in der Bedingung kann der Borrow-Checker zuschlagen:
let mut v = vec![1, 2, 3];
while let Some(x) = v.pop() {
println!("{x}");
}
// ok — pop() hält die &mut-Borrow nur für die Dauer eines Iterations-Schritts# let mut v = vec![1, 2, 3];
// Fehler: v ist in der Bedingung als &mut geborgt
// while let Some(x) = v.iter_mut().next() {
// // v hier nicht nochmal nutzbar
// }Bei iter_mut().next() lebt die iter_mut-Borrow für die gesamte while-Bedingung — und sperrt die Vec während der gesamten Schleife. Lösung: pop() (verbraucht), oder explizit into_iter()-Iteration mit for.
Infinite Loop durch falsche Bedingung
fn main() {
let n = 5;
// while n > 0 { // Endlos-Schleife!
// println!("{n}"); // n wird nie kleiner
// }
}n ist nicht mut und wird nirgends verändert — die Bedingung bleibt für immer true. Ohne Schreibzugriff auf n muss mut her und eine Dekrement-Operation im Body.
Off-by-One
let v = [10, 20, 30];
let mut i = 0;
while i <= v.len() { // <= statt < — index out of bounds beim letzten Schritt
println!("{}", v[i]);
i += 1;
}Im Allgemeinen sind explizite Index-Loops in Rust selten — for auf einem Iterator ist klarer und sicherer. Wenn doch ein Index gebraucht wird: for i in 0..v.len() mit Range, oder for (i, x) in v.iter().enumerate().
Praxis: while und while let im echten Code
Drain einer Worker-Queue
struct Job { id: u64, payload: String }
fn verarbeite_alle(queue: &mut Vec<Job>) {
while let Some(job) = queue.pop() {
println!("Job {} verarbeitet: {}", job.id, job.payload);
}
}
fn main() {
let mut q = vec![
Job { id: 1, payload: "A".into() },
Job { id: 2, payload: "B".into() },
];
verarbeite_alle(&mut q);
assert!(q.is_empty());
}while let Some(job) = queue.pop() ist das idiomatische „leere den Container und arbeite jedes Element ab". Am Ende ist queue leer.
Streaming-Read aus einer Datei
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn verarbeite_zeilen(pfad: &str) -> io::Result<usize> {
let file = File::open(pfad)?;
let mut reader = BufReader::new(file);
let mut line = String::new();
let mut count = 0;
while reader.read_line(&mut line)? > 0 {
if !line.trim().is_empty() {
count += 1;
}
line.clear();
}
Ok(count)
}read_line gibt die Anzahl gelesener Bytes zurück — 0 bedeutet EOF. Die while-Bedingung kombiniert Lesen und Test in einem Ausdruck. Wichtig: line.clear() am Ende, sonst akkumuliert der Buffer.
Channel mit recv-Timeout
use std::sync::mpsc::Receiver;
use std::time::Duration;
fn verarbeite_bis_idle<T: std::fmt::Debug>(rx: Receiver<T>) {
while let Ok(msg) = rx.recv_timeout(Duration::from_millis(200)) {
println!("Erhalten: {msg:?}");
}
// Timeout oder Channel geschlossen — alles abgearbeitet
}recv_timeout gibt Ok(msg) bei Erfolg, Err(Timeout) oder Err(Disconnected) bei Ende. while let Ok(msg) läuft so lange wie Nachrichten kommen, und beendet bei jeder Art von Fehler.
Polling auf File-System-Änderung
use std::fs;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
fn warte_auf_aenderung(pfad: &str, start: SystemTime) -> std::io::Result<()> {
while fs::metadata(pfad)?.modified()? <= start {
sleep(Duration::from_secs(1));
}
Ok(())
}Klassisches Polling-Pattern: prüfe Bedingung, schlafe kurz, prüfe wieder. while mit dem Vergleich passt strukturell perfekt.
State-Machine mit while
#[derive(Debug, PartialEq)]
enum State { Init, Connecting, Authenticated, Done }
fn main() {
let mut state = State::Init;
while state != State::Done {
state = match state {
State::Init => {
println!("Initialisiere...");
State::Connecting
}
State::Connecting => {
println!("Verbinde...");
State::Authenticated
}
State::Authenticated => {
println!("Arbeite...");
State::Done
}
State::Done => unreachable!(),
};
}
}while state != State::Done ist die natürliche Form für „solange noch nicht im Endzustand". Der match im Body bestimmt den nächsten State.
Binäre Datei Byte-für-Byte
fn finde_magic(bytes: &[u8], magic: &[u8]) -> Option<usize> {
if bytes.len() < magic.len() { return None; }
let mut i = 0;
let max = bytes.len() - magic.len();
while i <= max {
if &bytes[i..i + magic.len()] == magic {
return Some(i);
}
i += 1;
}
None
}Bewusst manuell statt windows(n).position(...) — wenn die Performance im inneren Loop kritisch ist und der Compiler den manuellen Code besser auflöst.
Pagination mit while
struct Page { items: Vec<String>, next_token: Option<String> }
fn lade_alle(api: impl Fn(Option<&str>) -> Page) -> Vec<String> {
let mut alle = Vec::new();
let mut next: Option<String> = None;
loop {
let page = api(next.as_deref());
alle.extend(page.items);
match page.next_token {
Some(t) => next = Some(t),
None => break,
}
}
alle
}Hier ist loop mit break besser als while, weil die Schleifen-Bedingung erst durch page.next_token bekannt wird. while next.is_some() wäre möglich, aber unklarer, weil die erste Iteration mit next == None startet.
Häufige Stolperfallen
while liefert immer ().
Anders als loop kannst du break <wert> in einer while nicht nutzen. Wer eine Schleife mit Wert-Rückgabe braucht: loop.
while let mit iter_mut bricht oft den Borrow Checker.
Die Bedingung borgt den Container für die ganze Schleife. Bei pop() oder drain() (verbrauchende Operationen) ist alles ok — bei iter_mut().next() typischerweise nicht. Lösung: for mit iter_mut(), oder die Methodenkette umstrukturieren.
Vergessene Mutation = Endlos-Schleife.
Klassiker: while n > 0 { ... } ohne n -= 1 im Body. Der Compiler kann das nicht warnen — n ist eventuell durch ein anderes Stück Code beeinflusst. Wer eine Bedingung schreibt, sollte sicherstellen, dass sie irgendwann false werden kann.
while-Schleifen lassen sich oft durch Iteratoren ersetzen.
Eine while i < v.len() { ... v[i] ...; i += 1; } ist fast immer als for x in &v { ... } lesbarer und sicherer. Idiomatisches Rust meidet manuelle Index-Variablen, wo es geht.
while let ist nicht das gleiche wie loop { match ... }.
while let Some(x) = iter.next() beendet bei None. loop { match iter.next() { Some(x) => ..., None => break } } ist äquivalent, aber verbose. Wo while let ausreicht, ist es lesbarer.
while ohne Body-Side-Effect ist ein Bug.
while cond {} ist meist ein Busy-Wait, der eine CPU-Kern verbrennt. Wer auf eine Bedingung von außen wartet: ein Sleep im Body, ein Channel-Receive, oder ein Condvar — sonst läuft das Programm heiß.
while-Pattern in Bedingungen wird seit Edition 2024 erweitert.
let chains — also if let Some(a) = x && let Some(b) = y — sind stable. In while funktioniert das ähnlich: while let Some(line) = reader.read_line() && line.starts_with("#") ist ein gültiges Pattern. Erlaubt feinere Schleifen-Conditions.
Weiterführende Ressourcen
Externe Quellen
- The Rust Book – while
- Rust Reference – while expressions
- Rust Reference – while let
- Rust by Example – while