Iteratori in Rust
Gli iteratori in Rust sono un pattern fondamentale per elaborare sequenze di elementi in modo lazy (pigro) e con zero overhead grazie alle ottimizzazioni del compilatore.
Il Trait Iterator
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Ogni iteratore implementa next(), che restituisce Some(valore) o None quando la sequenza è terminata.
Creare Iteratori da Collezioni
let v = vec![1, 2, 3];
// iter() - riferimenti immutabili (&T)
for val in v.iter() {
println!("{}", val);
}
// iter_mut() - riferimenti mutabili (&mut T)
let mut v = vec![1, 2, 3];
for val in v.iter_mut() {
*val *= 2;
}
// into_iter() - prende ownership (T)
for val in v.into_iter() {
println!("{}", val);
}
// v non è più utilizzabile
Adattatori (Lazy)
Gli adattatori trasformano un iteratore in un altro senza eseguire nulla fino al consumo:
let numeri = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map - trasforma ogni elemento
let doppi: Vec<i32> = numeri.iter().map(|x| x * 2).collect();
// filter - seleziona elementi
let pari: Vec<&i32> = numeri.iter().filter(|x| *x % 2 == 0).collect();
// flat_map - mappa e appiattisce
let parole: Vec<&str> = vec!["ciao mondo", "hello world"]
.iter()
.flat_map(|s| s.split_whitespace())
.collect();
// enumerate - aggiunge indice
for (i, val) in numeri.iter().enumerate() {
println!("{}: {}", i, val);
}
// zip - combina due iteratori
let nomi = vec!["Alice", "Bob"];
let eta = vec![25, 30];
let persone: Vec<_> = nomi.iter().zip(eta.iter()).collect();
// take e skip
let primi_tre: Vec<&i32> = numeri.iter().take(3).collect();
let senza_primi: Vec<&i32> = numeri.iter().skip(3).collect();
// chain - concatena iteratori
let tutti: Vec<i32> = (1..4).chain(7..10).collect();
// [1, 2, 3, 7, 8, 9]
Consumatori
I consumatori eseguono l’iteratore e producono un risultato:
let numeri = vec![1, 2, 3, 4, 5];
let somma: i32 = numeri.iter().sum(); // 15
let conteggio = numeri.iter().count(); // 5
let minimo = numeri.iter().min(); // Some(1)
let massimo = numeri.iter().max(); // Some(5)
// fold - riduzione personalizzata
let prodotto = numeri.iter().fold(1, |acc, x| acc * x); // 120
// any e all
let ha_pari = numeri.iter().any(|x| x % 2 == 0); // true
let tutti_positivi = numeri.iter().all(|x| *x > 0); // true
// find e position
let primo_pari = numeri.iter().find(|x| *x % 2 == 0); // Some(2)
let pos = numeri.iter().position(|x| *x == 3); // Some(2)
// for_each
numeri.iter().for_each(|x| println!("{}", x));
// collect in diversi contenitori
let set: std::collections::HashSet<_> = numeri.iter().collect();
let stringa: String = ['a', 'b', 'c'].iter().collect();
Iteratore Personalizzato
struct Contatore {
valore: u32,
massimo: u32,
}
impl Contatore {
fn new(massimo: u32) -> Self {
Contatore { valore: 0, massimo }
}
}
impl Iterator for Contatore {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.valore < self.massimo {
self.valore += 1;
Some(self.valore)
} else {
None
}
}
}
fn main() {
let somma: u32 = Contatore::new(5)
.zip(Contatore::new(5).skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
println!("{}", somma); // 18
}
Lazy Evaluation
Gli adattatori non fanno nulla finché un consumatore non li attiva:
let v = vec![1, 2, 3];
// Questo NON stampa nulla (lazy)
v.iter().map(|x| {
println!("{}", x);
x * 2
});
// Questo SÌ (collect consuma l'iteratore)
let risultato: Vec<_> = v.iter().map(|x| x * 2).collect();
Conclusione
Gli iteratori sono il modo idiomatico in Rust per elaborare sequenze di dati. Grazie alla lazy evaluation e alla monomorfizzazione, le catene di iteratori sono ottimizzate dal compilatore allo stesso livello dei cicli scritti a mano. Preferisci gli iteratori ai cicli for manuali per codice più leggibile, componibile e performante.