Zum Inhalt springen

Iteratoren in Rust

Ein Iterator ist alles, was eine Folge von Werten erzeugen kann – einen nach dem anderen. Du benutzt Iteratoren schon seit der Seite For-Schleifen, auch wenn wir sie bisher nicht so genannt haben. Hinter jeder for-Schleife über eine Sammlung steckt ein Iterator.

fn main() {
let numbers = vec![1, 2, 3];
let mut iter = numbers.iter();
println!("{:?}", iter.next()); // Some(1)
println!("{:?}", iter.next()); // Some(2)
println!("{:?}", iter.next()); // Some(3)
println!("{:?}", iter.next()); // None
}

Ausgabe:

Some(1)
Some(2)
Some(3)
None
▶ Selbst ausprobieren

.next() gibt eine Option zurück: Some(value) für jedes Element und None, sobald keine mehr übrig sind. Eine for-Schleife macht im Hintergrund nichts anderes, als immer wieder .next() aufzurufen, bis sie None bekommt.

.map() nimmt ein Closure und wendet es auf jedes Element an. Das Ergebnis ist ein neuer Iterator:

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);
}

Ausgabe:

[2, 4, 6, 8, 10]
▶ Selbst ausprobieren

.collect() macht aus dem Iterator wieder eine echte Sammlung (hier einen Vec<i32>). Ohne .collect() hättest du nur einen Iterator, der noch gar nichts ausgewertet hat.

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<&i32> = numbers.iter().filter(|n| **n % 2 == 0).collect();
println!("{:?}", evens);
}

Ausgabe:

[2, 4, 6]
▶ Selbst ausprobieren

Das doppelte **n sieht ungewohnt aus. .filter() übergibt dem Closure eine Referenz auf eine Referenz (&&i32). Deshalb musst du zweimal dereferenzieren, um mit einer normalen Zahl vergleichen zu können. Das ist eine typische kleine Eigenheit – die genaue Erklärung brauchst du jetzt noch nicht.

Jetzt zahlt sich das Verketten von Iterator-Methoden aus. Du kannst mehrere Schritte zu einer gut lesbaren Pipeline verbinden:

fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers
.iter()
.filter(|n| **n % 2 == 0) // gerade Zahlen behalten
.map(|n| n * 10) // jede mit 10 multiplizieren
.collect();
println!("{:?}", result);
}

Ausgabe:

[20, 40, 60]
▶ Selbst ausprobieren

Vergleich das mal mit derselben Logik als for-Schleife, die Werte in einen leeren Vec schiebt. Das wäre nicht falsch. Aber die verkettete Version liest sich fast wie deine Beschreibung: „filtern, dann multiplizieren, dann einsammeln“.

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let total: i32 = numbers.iter().sum();
let count = numbers.iter().count();
let has_even = numbers.iter().any(|n| n % 2 == 0);
println!("Summe: {total}, Anzahl: {count}, gerade Zahl dabei: {has_even}");
}

Ausgabe:

Summe: 15, Anzahl: 5, gerade Zahl dabei: true
▶ Selbst ausprobieren

.sum() zählt alles zusammen, .count() sagt dir, wie viele Elemente es gibt, und .any() beantwortet die Frage „Passt mindestens ein Element?“. Das sind nur ein paar der Methoden, die jeder Iterator hat. Es gibt noch viele mehr, etwa enumerate, zip, take und rev. Auf der Seite Eigene Iteratoren baust du deinen eigenen Iterator und siehst, dass er all diese Methoden geschenkt bekommt.

Nimm die Zahlen von 1 bis 10, behalte nur die ungeraden, quadriere jede und zähle alle zusammen. Mach das in einer einzigen Iterator-Kette, ohne mut-Variable. Tipp: Verkette .filter(), .map() und .sum() und gib dem Ergebnis einen Typ wie let total: i32.

Lösung anzeigen
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let total: i32 = numbers
.iter()
.filter(|n| **n % 2 == 1) // behält 1, 3, 5, 7, 9
.map(|n| n * n) // 1, 9, 25, 49, 81
.sum();
println!("Summe der ungeraden Quadrate: {total}");
}

Ausgabe:

Summe der ungeraden Quadrate: 165
▶ Selbst ausprobieren

Rust braucht das : i32 bei total, weil .sum() in mehrere Zahlentypen hinein addieren kann und nicht erraten kann, welchen du willst.

Fang mit vec!["the", "quick", "brown", "fox", "jumps"] an. Bau daraus einen neuen Vec<String>, der nur die Wörter mit mehr als 3 Buchstaben enthält, in Großbuchstaben, und gib ihn mit {:?} aus. Tipp: .filter(|w| w.len() > 3) behält die langen Wörter, und .to_uppercase() macht aus einem &str einen String in Großbuchstaben.

Lösung anzeigen
fn main() {
let words = vec!["the", "quick", "brown", "fox", "jumps"];
let loud: Vec<String> = words
.iter()
.filter(|w| w.len() > 3)
.map(|w| w.to_uppercase())
.collect();
println!("{:?}", loud);
}

Ausgabe:

["QUICK", "BROWN", "JUMPS"]
▶ Selbst ausprobieren

words selbst bleibt unverändert. .iter() leiht es nur aus, also kannst du words nach der Kette weiter benutzen. Die Wörter sind hier übrigens absichtlich englisch: .len() zählt Bytes, und Umlaute wie „ü“ belegen in UTF-8 zwei Bytes – mit deutschen Wörtern würde das Ergebnis leicht anders aussehen, als du erwartest.

  • Ein Iterator liefert Werte einen nach dem anderen über .next(), das eine Option zurückgibt.
  • Hinter for-Schleifen stecken Iteratoren.
  • .map() wandelt jedes Element um, .filter() behält nur die Elemente, die eine Bedingung erfüllen, und .collect() macht aus dem Ergebnis wieder eine echte Sammlung.
  • Verkettete Iterator-Methoden sind meistens klarer als eine Schleife mit einer veränderbaren Sammelvariable.

Kurzer Check

1. Was gibt .next() bei einem Iterator zurück?

2. Was macht .collect() am Ende einer Kette wie .iter().map(...).collect()?

3. Warum wird n in .filter(|n| **n % 2 == 0) zweimal dereferenziert?

4. Was steckt hinter einer for-Schleife, die über eine Sammlung läuft?

5. Was behält .filter() im entstehenden Iterator?

Punkte: 0 / 5