break und continue sind in Rust standard — sie unterbrechen oder überspringen Schleifen-Iterationen. Der spannende Teil sind die Labels: bei verschachtelten Schleifen kannst du gezielt aus einer äußeren brechen, ohne erst zur inneren zurückzukehren. Außerdem gibt es seit Rust 1.65 Block-Labels für Block-Expressions — eine Form von „frühem Ausstieg aus einem Berechnungs-Block", ohne die ganze Funktion zu verlassen.

break — Schleife beenden

Rust break
fn main() {
    for i in 0.. {
        if i * i > 100 {
            println!("Erstes Quadrat über 100: {} (i = {})", i * i, i);
            break;
        }
    }
}

break verlässt die innerste Schleife, in der es steht. Bei verschachtelten Schleifen unterbricht es nur die innere.

break mit Wert (nur in loop)

Wie im loop-Artikel gezeigt: break <wert>; funktioniert in loop, nicht in while oder for:

Rust break-Wert in loop
let resultat = loop {
    let n = berechne();
    if n > 100 { break n; }      // n wird zur Rückgabe der loop-Expression
};
# fn berechne() -> i32 { 200 }

continue — Iteration überspringen

Rust continue
fn main() {
    for i in 0..10 {
        if i % 2 == 0 { continue; }        // gerade Zahlen überspringen
        println!("{i}");
    }
    // 1, 3, 5, 7, 9
}

continue springt sofort zur nächsten Iteration. In for heißt das: nächstes Element. In while: zur Bedingungs-Prüfung. In loop: zum Anfang des Body.

Labels für verschachtelte Schleifen

In verschachtelten Loops kannst du Labels nutzen, um gezielt auf eine bestimmte Ebene zu zielen:

Rust Label-Syntax
fn main() {
    'aussen: for i in 0..5 {
        for j in 0..5 {
            if i + j == 6 {
                println!("Treffer bei i={i}, j={j}");
                break 'aussen;                 // verlässt beide Schleifen
            }
        }
    }
    println!("Fertig.");
}

Label-Schreibweise: ein Apostroph plus Name plus Doppelpunkt ('name:). Das gleiche '-Symbol wie bei Lifetimes — nicht zufällig, beide gehören zur „named scope"-Idee.

continue mit Label

Rust continue mit Label
fn main() {
    'zeilen: for i in 0..3 {
        for j in 0..3 {
            if j == 1 { continue 'zeilen; }    // Rest der inneren Schleife abbrechen,
                                               // nächste Zeile beginnen
            println!("{i},{j}");
        }
    }
    // 0,0
    // 1,0
    // 2,0
}

continue 'zeilen springt zum Anfang der äußeren Schleife — überspringt also den Rest der inneren komplett.

break mit Label und Wert

Auch bei verschachteltem loop funktioniert break 'label <wert>:

Rust Labeled break mit Wert
fn main() {
    let result = 'aussen: loop {
        let mut n = 0;
        loop {
            n += 1;
            if n > 100 {
                break 'aussen n * 10;      // verlässt äußere loop mit Wert
            }
        }
    };
    println!("{result}");                   // 1010
}

Sehr selten gebraucht, aber wenn doch — perfekt klar lesbar.

Block-Labels (seit Rust 1.65)

Ein Block-Label markiert einen normalen { ... }-Block. Innerhalb kannst du mit break 'label <wert> rausspringen — quasi ein „frühes return aus einem Block":

Rust Block-Label
fn main() {
    let kategorie = 'cat: {
        let score = 75;
        if score >= 90 { break 'cat "A"; }
        if score >= 80 { break 'cat "B"; }
        if score >= 70 { break 'cat "C"; }
        "F"
    };
    println!("{kategorie}");        // "C"
}

Mehrere Branch-Stellen, die alle einen Wert produzieren — ohne verschachteltes if-else und ohne hilfs-mut. Sehr nützlich bei komplexerer Klassifikations-Logik.

Praxis: break, continue, Labels in echtem Code

2D-Matrix-Suche mit Label

Rust Matrix-Suche
fn finde_in_matrix(matrix: &[Vec<i32>], ziel: i32) -> Option<(usize, usize)> {
    for (y, zeile) in matrix.iter().enumerate() {
        for (x, &wert) in zeile.iter().enumerate() {
            if wert == ziel { return Some((y, x)); }
        }
    }
    None
}

Hier reicht return statt break 'aussen — eleganter, weil das return direkt den Ergebnis-Wert mitliefert. Labels kommen ins Spiel, wenn nach der Suche noch Code in der Funktion läuft.

Validierungs-Pipeline mit early-continue

Rust Filter-Loop
struct Eintrag { id: u64, betrag_cent: i64, storniert: bool }

fn summiere_aktive(eintraege: &[Eintrag]) -> i64 {
    let mut total = 0;
    for e in eintraege {
        if e.storniert { continue; }           // storniert → überspringen
        if e.betrag_cent <= 0 { continue; }    // ungültig → überspringen
        total += e.betrag_cent;
    }
    total
}

Mehrere continue-Filter am Anfang der Iteration sind eine sehr lesbare Form von „Skip ungültige Einträge".

Cancel-Pattern mit labeled break

Rust Cancellable Worker
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;

fn verarbeite(jobs: &[u64], cancel: Arc<AtomicBool>) {
    'main: for job in jobs {
        for _step in 0..1000 {
            if cancel.load(Ordering::Relaxed) {
                println!("Abbruch — fertig nach Job {job}");
                break 'main;
            }
            // ... arbeite an einem Step des Jobs ...
        }
    }
}

Innere Schleife verarbeitet Steps eines Jobs, äußere iteriert über Jobs. Bei Cancel-Signal: aus beiden Schleifen raus. break 'main ist klarer als zwei separate Flags.

Retry mit Backoff und Max-Versuchen

Rust Retry-Loop
use std::thread::sleep;
use std::time::Duration;

fn api_call_mit_retry(url: &str) -> Option<String> {
    let mut versuch = 0;
    let result = 'retry: loop {
        versuch += 1;
        match hole(url) {
            Ok(body) => break 'retry Some(body),
            Err(_) if versuch >= 5 => break 'retry None,
            Err(_) => {
                sleep(Duration::from_millis(100 * 2u64.pow(versuch)));
            }
        }
    };
    result
}
# fn hole(_: &str) -> Result<String, ()> { Err(()) }

Das Label 'retry ist hier semantisch — es macht klar, was der break verlässt. Sehr nützlich in komplexerem Code.

Multi-Level Pagination

Rust Pagination mit Outer-Break
fn finde_user(seiten: impl Iterator<Item = Vec<User>>, ziel_id: u64) -> Option<User> {
    'pagination: for seite in seiten {
        for user in seite {
            if user.id == ziel_id {
                return Some(user);              // return statt break ist hier sauberer
            }
            if user.id > ziel_id * 10 {
                break 'pagination;              // Daten sortiert, kein Treffer mehr möglich
            }
        }
    }
    None
}
# struct User { id: u64 }

Der break 'pagination ist nützlich, wenn man früh aussteigen kann, ohne sofort etwas zurückzugeben — etwa für „weitere Suche unmöglich" in sortierten Daten.

Komplexe Klassifikation mit Block-Label

Rust Block-Label
struct Order { betrag_cent: u32, ist_premium: bool, anzahl_artikel: u32 }

fn rabatt_kategorie(o: &Order) -> &'static str {
    'kat: {
        if o.ist_premium && o.betrag_cent >= 50_000 { break 'kat "VIP"; }
        if o.betrag_cent >= 20_000 { break 'kat "Gold"; }
        if o.anzahl_artikel >= 10 { break 'kat "Bulk"; }
        if o.ist_premium { break 'kat "Premium"; }
        "Standard"
    }
}

Ohne Block-Label hätte man entweder eine tief verschachtelte if-else-Kette oder verschiedene returns — letzteres geht nur in einer eigenen Funktion. Block-Labels machen die „frühen Ausstiegs"-Logik direkt im Inline-Block möglich.

Suche mit Threshold-Check

Rust Sliding-Window
fn finde_anomalie(werte: &[f64], threshold: f64) -> Option<usize> {
    for (i, &v) in werte.iter().enumerate() {
        if v.is_nan() { continue; }                  // NaN überspringen
        if v.abs() > threshold {
            return Some(i);
        }
    }
    None
}

continue für „ignoriere", return für „Treffer gefunden". Klassisches Such-Pattern.

Häufige Stolperfallen

break ohne Label verlässt nur die innerste Schleife.

Klassische Stolperfalle bei verschachtelten Loops. Wer aus beiden raus will: entweder Label oder eine Funktion mit return. Letztres ist oft die elegantere Lösung.

Labels haben das gleiche '-Sigil wie Lifetimes.

'aussen als Label und 'a als Lifetime sehen syntaktisch identisch aus. Im Kontext ist klar, was gemeint ist (Labels nur in Schleifen/Blocks, Lifetimes in Typ-Positionen). Tooling und Editoren highlighten beide unterschiedlich.

continue mit Label ist seltener als break mit Label.

Meistens reicht ein normales continue. Labeled-continue lohnt sich vor allem bei tief verschachtelten Schleifen, wo man explizit die Iteration einer äußeren Ebene fortsetzen will.

Block-Labels sind seit Rust 1.65 stable.

Wer mit älteren Toolchains arbeitet (sehr selten heute), muss auf Helper-Funktionen oder if-else-Ketten ausweichen. In modernem Code sind Block-Labels die idiomatische Form für „frühen Ausstieg aus einem Berechnungs-Block".

return ist oft die bessere Wahl als break mit Label.

Wenn die ganze umliegende Funktion mit dem Schleifen-Ergebnis fertig ist, ist return Some(...) klarer als break 'aussen plus Hilfs-mut außerhalb. Labels brauchst du nur, wenn nach der Schleife noch Code in der gleichen Funktion läuft.

break aus einem match-Arm bezieht sich auf die umliegende Schleife.

for x in &v { match x { Some(n) => break, None => {} } }break verlässt die for-Schleife, nicht den match. Das ist die Standard-Semantik; ein nakter break zielt immer auf die innerste Schleife, egal wie viele match dazwischen sind.

Clippy warnt vor ungenutzten Labels.

Ein definiertes Label ohne korrespondierendes break/continue 'label ist ein Code-Smell. clippy::unused_label (in restriction) findet das.

break und continue haben Typ !.

Beide sind divergierende Expressions. Damit lassen sie sich an Stellen einsetzen, wo eigentlich ein Wert erwartet wird (Match-Armen, if-Branches) — sie coercen zu jedem Typ.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Kontrollfluss

Zur Übersicht