Iterator în Rust

Trait-ul care descrie orice șir de valori produse pe rând: next() dă următoarea valoare, sau None la final.

Sintaxă
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // plus zeci de metode cu implementare implicită: map, filter, sum, collect...
}

Ce face Iterator

Un iterator este un obiect care produce valori una câte una. Singura metodă obligatorie este next(): returnează Some(valoare) cât timp mai sunt valori și None când s-au terminat. Tipul valorilor este Item. Iteratorul își schimbă starea la fiecare apel, de aceea next cere &mut self.

Toate celelalte metode (map, filter, sum, collect și peste 70 de altele) sunt construite peste next, deci orice tip care implementează Iterator le primește gratuit. Bucla for apelează next până la None.

Iteratorii din Rust sunt leneși: v.iter().map(...) nu calculează nimic, ci doar descrie calculul. Valorile sunt produse abia când cineva le cere: o buclă for sau o metodă „consumatoare” precum collect::<Vec<_>>(), sum(), count(). Datorită acestui lucru, lanțurile de adaptoare sunt la fel de rapide ca o buclă scrisă de mână și pot lucra chiar și cu șiruri infinite.

Ce returnează

next() returnează Option<Self::Item>.

Exemple

Exemplul 1: next() apelat de mână

iter() pe un vector produce referințe (&i32). După ultimul element, next returnează None.

fn main() {
    let note = vec![9, 7, 10];
    let mut it = note.iter();
    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());
    println!("{:?}", it.next());
}

Rezultat

Some(9)
Some(7)
Some(10)
None

Exemplul 2: Un iterator propriu: numărătoare inversă

Implementăm doar next; sum, filter și collect vin automat din trait.

struct Numaratoare {
    ramas: u32,
}

impl Iterator for Numaratoare {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        if self.ramas == 0 {
            None
        } else {
            self.ramas -= 1;
            Some(self.ramas + 1)
        }
    }
}

fn main() {
    let lansare = Numaratoare { ramas: 5 };
    for n in lansare {
        print!("{n} ");
    }
    println!("start!");

    let suma: u32 = Numaratoare { ramas: 10 }.sum();
    let pare: Vec<u32> = Numaratoare { ramas: 10 }.filter(|n| n % 2 == 0).collect();
    println!("suma 1..10 = {suma}, pare = {:?}", pare);
}

Rezultat

5 4 3 2 1 start!
suma 1..10 = 55, pare = [10, 8, 6, 4, 2]

Exemplul 3: Iteratorii sunt leneși

Crearea lanțului nu afișează nimic. Abia collect cere valorile. În al doilea lanț, find se oprește la primul rezultat, deci 4 nu mai este procesat deloc.

fn main() {
    let v = vec![1, 2, 3, 4];
    let lant = v.iter().map(|x| {
        println!("  procesez {x}");
        x * 10
    });
    println!("lanțul a fost creat, dar nimic nu s-a procesat încă");
    let rezultat: Vec<i32> = lant.collect();
    println!("rezultat: {:?}", rezultat);

    println!("caut primul pătrat mai mare de 5:");
    let gasit = v
        .iter()
        .map(|x| {
            println!("  calculez {x}²");
            x * x
        })
        .find(|&p| p > 5);
    println!("găsit: {:?}", gasit);
}

Rezultat

lanțul a fost creat, dar nimic nu s-a procesat încă
  procesez 1
  procesez 2
  procesez 3
  procesez 4
rezultat: [10, 20, 30, 40]
caut primul pătrat mai mare de 5:
  calculez 1²
  calculez 2²
  calculez 3²
găsit: Some(9)

Greșeli frecvente

  • Construiești un lanț (v.iter().map(...)) și nu îl consumi. Nimic nu se execută, iar compilatorul avertizează: unused `Map` that must be used: iterators are lazy and do nothing unless consumed. Adaugă collect, for_each sau o buclă for.
  • Declari iteratorul fără mut și apelezi next(): cannot borrow `it` as mutable. next modifică starea iteratorului.
  • Încerci să parcurgi de două ori același iterator. După prima parcurgere este epuizat și dă doar None. Creează altul (v.iter() din nou) sau colectează valorile într-un vector.

Comenzi înrudite

  • IntoIterator: Trait-ul tipurilor care pot fi transformate într-un iterator; este ce folosește bucla for pentru a parcurge o valoare.
  • Iterator::collect: Consumă iteratorul și adună valorile într-o colecție: Vec, String, HashMap, HashSet, chiar Result sau Option.
  • Iterator::map: Transformă fiecare valoare a iteratorului cu o funcție și dă un iterator cu rezultatele; nu calculează nimic până la consumare.
  • Iterator::filter: Păstrează doar elementele pentru care condiția dată returnează true; restul sunt sărite.
  • Vec::iter: Cele trei moduri de a parcurge un vector: iter dă referințe, iter_mut dă referințe mutabile, into_iter dă elementele.
  • DoubleEndedIterator: Iterator care poate produce valori și de la coadă (next_back); permite rev(), rfind() și parcurgerea din ambele capete.