for ist in Rust die zentrale Schleifen-Form. Anders als in C/Java ist sie keine Zähl-Schleife mit init; cond; incr — sie arbeitet auf jedem IntoIterator. Damit umfasst sie Ranges (0..n), Sammlungen (Vec, Array, HashMap), eigene Iteratoren und alles, was die Iterator-Adapter-Kette produziert. Das ist gleichzeitig leistungsfähiger und sicherer: keine off-by-one-Fehler, keine vergessenen Inkremente, dafür der gesamte Iterator-Adapter-Reichtum (map, filter, chain, zip, enumerate, ...) direkt verfügbar.
Grundform
fn main() {
for i in 0..5 {
println!("{i}");
}
// 0, 1, 2, 3, 4
}Das 0..5 ist eine Range<i32> — eine half-open range, exklusiv am Ende. Sie implementiert Iterator, also kann for darüber laufen.
Für inklusive Ranges: 0..=5 (gleich 0, 1, 2, 3, 4, 5).
Die drei Iteration-Modi für Collections
Vec, Array, HashMap und Co. bieten drei Iteration-Varianten:
fn main() {
let v = vec![10, 20, 30];
// Über Referenzen: &T
for x in v.iter() {
println!("{x}"); // x: &i32
}
// Über mutable Referenzen: &mut T
let mut v = vec![10, 20, 30];
for x in v.iter_mut() {
*x *= 2;
}
// v ist jetzt [20, 40, 60]
// Über Werte: T (verbraucht die Sammlung)
let v = vec![10, 20, 30];
for x in v.into_iter() {
println!("{x}"); // x: i32
}
// v ist hier nicht mehr verfügbar
}Faustregel:
iter()— lesender Zugriff. Standardfall.iter_mut()— mutieren ohne zu konsumieren.into_iter()— Werte herausnehmen, Sammlung verbrauchen.
Syntaktische Kurzformen
let v = vec![1, 2, 3];
for x in &v { /* x: &i32 — wie v.iter() */ }
for x in &mut v.clone() { /* x: &mut i32 — wie v.iter_mut() */ }
for x in v { /* x: i32 — wie v.into_iter() */ }for x in collection ohne & oder iter() ist seit Edition 2021 sicher — auch Arrays implementieren by-value-Iteration. In Edition 2018 wäre for x in [1, 2, 3] über Referenzen gelaufen.
enumerate und zip
Zwei extrem häufige Patterns:
enumerate — Index plus Wert
fn main() {
let woerter = ["der", "die", "das"];
for (i, w) in woerter.iter().enumerate() {
println!("{i}: {w}");
}
// 0: der
// 1: die
// 2: das
}Idiomatischer Ersatz für klassische for (int i = 0; i < n; i++)-Schleifen aus C.
zip — parallel iterieren
fn main() {
let namen = ["Anna", "Ben", "Clara"];
let alter = [28, 35, 41];
for (name, jahre) in namen.iter().zip(alter.iter()) {
println!("{name}: {jahre}");
}
}zip paart Elemente aus zwei Iteratoren — endet, sobald einer der beiden leer ist. Wenn die Iteratoren unterschiedliche Länge haben, ist das das gewollte Verhalten (gut für Defensive-Programmierung).
Ranges im Detail
for i in 0..5 { /* 0,1,2,3,4 */ } // Range
for i in 0..=5 { /* 0,1,2,3,4,5 */ } // RangeInclusive
for i in (0..5).rev() { /* 4,3,2,1,0 */ } // umgekehrt
for i in (0..10).step_by(2) { /* 0,2,4,6,8 */ } // mit Step
// Range auf anderen Integer-Typen
for i in 0u8..255 { /* i: u8 */ }
for i in 0i64..1_000_000_000_i64 { /* i: i64 */ }Ranges sind lazy — 0..1_000_000_000_i64 allokiert nichts; jede Iteration produziert die nächste Zahl on-the-fly.
Iterator-Adapter statt for
Oft ist die Iterator-Form kompakter als for:
let zahlen = vec![1, 2, 3, 4, 5];
// Mit for
let mut summe = 0;
for n in &zahlen {
if *n % 2 == 0 { summe += n; }
}
// Mit Iterator-Chain
let summe2: i32 = zahlen.iter().filter(|n| **n % 2 == 0).sum();Beide sind valid. Iterator-Chains sind oft eleganter und lazy — sie evaluieren nur, was sie brauchen. for ist klarer, wenn Side-Effects oder Kontroll-Fluss gebraucht werden.
Faustregel: pure Berechnung → Iterator-Chain. Side-Effects (Print, I/O) → for.
Praxis: for-Schleifen im echten Code
Mittelwert eines Vektors
fn mittel(werte: &[f64]) -> Option<f64> {
if werte.is_empty() { return None; }
let summe: f64 = werte.iter().sum();
Some(summe / werte.len() as f64)
}Eine Iterator-Chain mit sum() ist eleganter als ein for-Loop mit mut total.
Bild rotieren (90° im Uhrzeigersinn)
fn rotiere_90(bild: &[Vec<u8>]) -> Vec<Vec<u8>> {
let h = bild.len();
let w = bild[0].len();
let mut rotiert = vec![vec![0u8; h]; w];
for y in 0..h {
for x in 0..w {
rotiert[x][h - 1 - y] = bild[y][x];
}
}
rotiert
}Klassisches Doppel-Loop-Pattern für 2D-Operationen. Mit Range über Indices direkt.
CSV-Header und -Body trennen
fn parse_csv(zeilen: &[&str]) -> (Vec<&str>, Vec<Vec<&str>>) {
let mut iter = zeilen.iter();
let header: Vec<&str> = iter.next()
.map(|h| h.split(',').collect())
.unwrap_or_default();
let body: Vec<Vec<&str>> = iter
.map(|zeile| zeile.split(',').collect())
.collect();
(header, body)
}Mischung aus iter().next() und map().collect() — ohne Schleife, aber mit derselben Logik.
Histogramm bauen
use std::collections::HashMap;
fn histogramm(text: &str) -> HashMap<char, u32> {
let mut counts = HashMap::new();
for c in text.chars() {
*counts.entry(c).or_insert(0) += 1;
}
counts
}
fn main() {
let h = histogramm("hallo");
assert_eq!(h.get(&'l'), Some(&2));
}entry().or_insert(0) += 1 ist das idiomatische „erhöhe Zähler in HashMap" — atomar, ohne extra Lookup.
Server-Log mit Latenz-Analyse
struct LogEintrag { latency_ms: u32, status: u16 }
fn analysiere(eintraege: &[LogEintrag]) -> (u32, u32, u32) {
let mut min = u32::MAX;
let mut max = 0;
let mut total: u64 = 0;
let mut count = 0;
for e in eintraege {
if e.status >= 500 { continue; } // Server-Fehler überspringen
min = min.min(e.latency_ms);
max = max.max(e.latency_ms);
total += e.latency_ms as u64;
count += 1;
}
if count == 0 { return (0, 0, 0); }
(min, max, (total / count as u64) as u32)
}continue überspringt einen unerwünschten Eintrag — sehr typisch in Log-Filtern.
Parallele Vektoren addieren
fn addiere(a: &[f64], b: &[f64]) -> Vec<f64> {
a.iter().zip(b.iter())
.map(|(x, y)| x + y)
.collect()
}
// Oder als for-Loop:
fn addiere_v2(a: &[f64], b: &[f64]) -> Vec<f64> {
let mut result = Vec::with_capacity(a.len().min(b.len()));
for (x, y) in a.iter().zip(b.iter()) {
result.push(x + y);
}
result
}zip + map + collect ist die idiomatische Form. Die for-Variante ist nicht falsch, aber verbose für reine Berechnung.
Tabellen-Format mit Spalten-Padding
fn drucke_tabelle(daten: &[(String, u32)]) {
let max_name = daten.iter().map(|(n, _)| n.len()).max().unwrap_or(0);
for (name, wert) in daten {
println!("{name:<width$} | {wert:>5}", width = max_name);
}
}Zweistufige Verarbeitung: erst Max-Breite finden (über Iterator), dann formatieren (über for).
Suche über mehrere Quellen mit chain
fn finde_in_beiden<'a>(a: &'a [String], b: &'a [String], gesucht: &str) -> Option<&'a String> {
for s in a.iter().chain(b.iter()) {
if s == gesucht { return Some(s); }
}
None
}chain verbindet zwei Iteratoren zu einem — eine for-Schleife läuft über beide nacheinander. Sehr nützlich für „suche über mehrere Sammlungen".
FAQ
Warum gibt es kein for (int i = 0; i < n; i++)?
Weil das fehleranfällig ist (off-by-one, vergessenes Inkrement) und Rust eine bessere Alternative hat: for i in 0..n. Wenn du wirklich C-Style Iteration brauchst (z. B. mit Schritt-Größe), gibt es for i in (0..n).step_by(2).
Soll ich iter() oder & nutzen?
Beides funktioniert: for x in v.iter() und for x in &v sind identisch. Idiomatisch ist for x in &v für reines Lesen, for x in &mut v für Mutation und for x in v (verbrauchend) — kürzer und klarer.
Wann for vs. Iterator-Chain?
for bei Side-Effects (println, I/O, Mutation), Iterator-Chain bei reinen Transformationen (map, filter, sum). Beide sind gleich schnell — der Compiler optimiert beide zum gleichen Maschinencode. Wahl nach Lesbarkeit.
Ist for i in 0..vec.len() idiomatisch?
Selten. Falls du nur die Werte brauchst: for x in &v. Falls du Index und Wert brauchst: for (i, x) in v.iter().enumerate(). Eine reine Index-Range zum Indizieren in den gleichen Vec ist meistens ein Code-Smell.
Kann ich aus for einen Wert zurückgeben?
Nein. for hat immer Typ (). Wer einen Wert braucht: entweder eine mut-Variable außerhalb füllen, oder die Iterator-Adapter (sum, fold, find, collect) nutzen, oder loop mit break <wert>.
continue springt zur nächsten Iteration.
Genau wie in C. In Rust besonders nützlich für Filter-Logik: for x in &v { if !ok(x) { continue; } verarbeite(x); }. Ist äquivalent zu for x in v.iter().filter(|x| ok(x)) { verarbeite(x); } — der Filter-Stil ist oft eleganter.
for-Range über große Werte ist nicht effizienter als manuell.
for i in 0..1_000_000_000 produziert die gleiche Maschinencode wie eine handgeschriebene Zähl-Schleife. Lazy-Generation hat keinen Overhead — der Compiler inlined alles.
Edition 2021 änderte for x in [1,2,3].
Vor 2021 lief das über Referenzen (x: &i32). Seit 2021 by-value (x: i32). Bei alten Codebases beim Migrieren auf 2024 darauf achten — cargo fix --edition korrigiert die meisten Stellen automatisch.
Weiterführende Ressourcen
Externe Quellen
- The Rust Book – for-Loops
- Rust Reference – for expressions
- std::iter::Iterator – Methoden-Doku
- std::ops::Range
- Rust by Example – for and range