Zum Inhalt springen

Iteratoren

Welche der folgenden Aussagen beschreibt am besten, warum Iteratoren als “träge” bezeichnet werden?

Träge bedeutet im Allgemeinen “führt keine Arbeit aus, bis sie angefordert wird”, und Iteratoren führen keine Berechnungen durch, bis Iterator::next aufgerufen wird.


Richtig/Falsch: Diese beiden Code-Snippets sind semantisch äquivalent, wobei iter ein Iterator ist.

Snippet 1:

while let Some(x) = iter.next() {
f(x);
}

Snippet 2:

for x in iter {
f(x);
}

Die For-Schleife ist ein syntaktischer Zucker für das while let , welches selbst ein Zucker für loop und break ist.


fn main() {
let v = vec![1, 2, 3, 4];
let a: Vec<_> = v.iter().filter(|x: &&i32| *x % 2 == 0).map(|x: &i32| x * 2).collect();
let b: Vec<_> = v.iter().map(|x: &i32| x * 2).filter(|x: &i32| x % 2 == 0).collect();
println!("{} {}", a[0], b[0]);
}

Die Reihenfolge der Iteratoren ist wichtig — ein Filter gefolgt von einem Map ist nicht dasselbe wie ein Map gefolgt von einem Filter!

Sie fragen sich vielleicht, warum der erste Filter *x verwendet und der zweite Filter nicht. v.iter() erzeugt einen Iterator<Item = &i32> . Der Aufruf von .filter() nimmt einen Iterator<Item = T> als Eingabe und übergibt &T an sein Prädikat. Daher ist x: &&i32 in Zeile 3. Die Rust-Standardbibliothek implementiert den Modulo-Operator % für &i32 auf der linken Seite (siehe die Dokumentation), aber nicht für &&i32 . Wir müssen also x einmal dereferenzieren, um es im Ausdruck *x % 2 zu verwenden.

Im Gegensatz dazu, wenn .map() in Zeile 4 einen Iterator<Item = T> als Eingabe nimmt, übergibt es T an seine Closure. Daher nimmt die Closure in map &i32 als Eingabe. Der Multiplikationsoperator * ist für &i32 implementiert, so dass x in x * 2 nicht dereferenziert werden muss. Die Operation x * 2 erzeugt einen Wert vom Typ i32 , so dass das Ergebnis des Maps ein Iterator<Item = i32> ist. Der filter nimmt dann x : &i32 , was ebenfalls keine Dereferenzierung für x % 2 benötigt. Jetzt wissen Sie Bescheid!