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

Rust Klassisches while
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:

Rust while let
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

SituationBevorzugte Form
Sequenz/Iterator durchlaufenfor
Bedingung am Anfang prüfbar, kein Wert nötigwhile
Pattern matcht (Option/Result drainen)while let
Bedingung erst im Body, oder Wert nötigloop 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:

Rust Drain-Pattern — funktioniert
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
Rust Iter-Pattern — funktioniert nicht
# 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

Rust Vergessene Mutation
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

Rust Index-Falle
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 seltenfor 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

Rust Job-Queue leeren
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

Rust Chunkweise 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

Rust Channel-Drain mit 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

Rust File-Mtime-Polling
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

Rust State-Machine
#[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

Rust Magic-Number-Suche
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

Rust API-Paginierung
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

/ Weiter

Zurück zu Kontrollfluss

Zur Übersicht