W Rust cała standardowa biblioteka kolekcji oparta jest na koncepcji iteratorów. Iterator to obiekt, który implementuje trait Iterator, w którym zdefiniowana jest metoda next(). Metoda ta zwraca następny element sekwencji danych (Option<T>, gdzie Some(T) to kolejna wartość, a None to koniec sekwencji).
Przykład standardowego iteratora:
let v = vec![1, 2, 3]; let mut iter = v.iter(); while let Some(x) = iter.next() { println!("{}", x); }
Adaptery iteratorów to metody (na przykład .map(), .filter(), .enumerate(), .take()), które zwracają nowe iteratory, przetwarzające wartości „w locie” zgodnie z przekazanymi funkcjami.
Własne iteratory implementuje się poprzez stworzenie struktury i zaimplementowanie dla niej traitu Iterator z własnym zachowaniem:
struct Counter { count: u8 } impl Iterator for Counter { type Item = u8; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }
Używaj standardowych adapterów, gdy są wystarczające do przetwarzania kolekcji. Implementacja własnego iteratora jest konieczna, jeśli:
Czy można tworzyć nieskończone iteratory? Co się stanie, gdy spróbujesz zebrać je w kolekcję, na przykład za pomocą
.collect()?
Odpowiedź: Tak, w Rust istnieją iteratory, takie jak std::iter::repeat, zwracające nieskończoną sekwencję:
let mut endless = std::iter::repeat(1); endless.next(); // zwróci Some(1) w nieskończoność
Jeśli spróbujesz zebrać taki iterator w kolekcję przez .collect(), program zawiesi się lub zakończy działanie z powodu przekroczenia pamięci, ponieważ iteracja nie zakończy się sama z siebie!
Historia
W projekcie REST API tablica z 1000 elementów była sortowana, a następnie użyto .iter().filter(|x| *x > 500), ale zamiast .collect::<Vec<_>>() zastosowano .fold(0, |acc, _| acc + 1) wewnątrz skomplikowanego adaptera, tracąc zrozumienie, czy iteracja zakończy się poprawnie. W rezultacie: losowe zawieszenia z powodu tego, że filtrowanie odbywało się na nieograniczonej leniwej iteracji z wewnętrznym błędem.
Historia
W jednym komercyjnym silniku do generowania unikalnych id przypadkowy programista postanowił zaimplementować własny iterator, zapominając zwrócić None po osiągnięciu limitu. W rezultacie iteracja wchodziła w nieskończoną pętlę, serwer w produkcji zużywał całą moc CPU i nie odpowiadał na zapytania.
Historia
W części frontendowej (moduł WebAssembly) używano iteratora z zagnieżdżonymi adapterami: .map().filter().skip(), i próbowano uzyskać wynik przez .collect() dla formularza. Po zmianie typu pod adapterem Rust wydał złożony błąd czasu kompilacji o niespójności typów, ponieważ zapomniano określić dokładny typ kolekcji: .collect::<Vec<_>>(). Problem został rozwiązany przez dodanie adnotacji, ale stracono kilka godzin na poszukiwanie przyczyny.