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
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:
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
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:
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
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>:
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":
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
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
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
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
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
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
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
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
- The Rust Book – Loops
- Rust Reference – break expressions
- Rust Reference – continue expressions
- Rust by Example – Nesting and labels
- Rust 1.65 Release Notes – Block-Labels