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.
Einen Iterator bekommen
Abschnitt betitelt „Einen Iterator bekommen“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.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 – jedes Element umwandeln
Abschnitt betitelt „map – jedes Element umwandeln“.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].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.
filter – nur bestimmte Elemente behalten
Abschnitt betitelt „filter – nur bestimmte Elemente behalten“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]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.
map und filter verketten
Abschnitt betitelt „map und filter verketten“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]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“.
Ein paar weitere nützliche Methoden
Abschnitt betitelt „Ein paar weitere nützliche Methoden“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.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.
Übungen
Abschnitt betitelt „Übungen“1. Summe der ungeraden Quadrate
Abschnitt betitelt „1. Summe der ungeraden Quadrate“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: 165Rust braucht das : i32 bei total, weil .sum() in mehrere Zahlentypen hinein addieren kann und nicht erraten kann, welchen du willst.
2. Die langen Wörter rufen
Abschnitt betitelt „2. Die langen Wörter rufen“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"]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.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Ein Iterator liefert Werte einen nach dem anderen über
.next(), das eineOptionzurü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
Abschnitt betitelt „Kurzer Check“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
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Eine Reihe von Elementen mit Iteratoren verarbeiten – Das Rust-Buch: die offizielle Einführung in
next,map,filterundcollect, mit weiteren Beispielen. - Performanz von Schleifen vs. Iteratoren – Das Rust-Buch: warum Iterator-Ketten genauso schnell sind wie eine von Hand geschriebene Schleife.
- Trait
Iterator– Dokumentation der Standardbibliothek (auf Englisch): die vollständige Liste der Iterator-Methoden, jede mit einem kleinen Beispiel. Lohnt sich zum Stöbern. - Iterators – Rust by Example (auf Englisch): ein kurzes, ausführbares Beispiel, das einen Fibonacci-Iterator baut.