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

Rust for über Range
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:

Rust iter / iter_mut / into_iter
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

Rust 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

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

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

Rust Range-Formen
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 lazy0..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:

Rust for vs. Iterator-Chain
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

Rust Stats
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)

Rust Matrix-Rotation
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

Rust CSV-Parse
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

Rust HashMap als Counter
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

Rust Log-Statistik
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

Rust Vektor-Addition
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

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

Rust 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

/ Weiter

Zurück zu Kontrollfluss

Zur Übersicht