Iteratori in Rust
Un iteratore è qualsiasi cosa capace di produrre una sequenza di valori, uno alla volta. Li usi già dalla pagina sul ciclo for, anche se non li chiamavi così: ogni ciclo for su una collezione funziona grazie a un iteratore che lavora dietro le quinte.
Ottenere un iteratore
Sezione intitolata “Ottenere un iteratore”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}Output:
Some(1)Some(2)Some(3)None.next() restituisce un Option: Some(valore) per ogni elemento e None quando gli elementi finiscono. Un ciclo for, in fondo, non fa altro che chiamare .next() più volte, finché non riceve None.
map: trasformare ogni elemento
Sezione intitolata “map: trasformare ogni elemento”.map() prende una closure e la applica a ogni elemento, producendo un nuovo iteratore:
fn main() { let numbers = vec![1, 2, 3, 4, 5]; let doubled: Vec<i32> = numbers.iter().map(|n| n * 2).collect();
println!("{:?}", doubled);}Output:
[2, 4, 6, 8, 10].collect() è il metodo che trasforma di nuovo l’iteratore in una collezione concreta (qui un Vec<i32>). Senza di lui avresti solo un iteratore fermo lì, mai eseguito.
filter: tenere solo alcuni elementi
Sezione intitolata “filter: tenere solo alcuni elementi”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);}Output:
[2, 4, 6]Il doppio **n sembra strano: .filter() passa alla closure un riferimento a un riferimento (&&i32), quindi serve dereferenziare due volte per confrontarlo con un numero normale. È una piccola stranezza comune; per ora non preoccuparti della spiegazione completa.
Concatenare map e filter
Sezione intitolata “Concatenare map e filter”Qui la concatenazione dei metodi degli iteratori comincia a ripagare: puoi unire più passaggi in un’unica pipeline facile da leggere.
fn main() { let numbers = vec![1, 2, 3, 4, 5, 6];
let result: Vec<i32> = numbers .iter() .filter(|n| **n % 2 == 0) // tieni i numeri pari .map(|n| n * 10) // moltiplica ognuno per 10 .collect();
println!("{:?}", result);}Output:
[20, 40, 60]Prova a confrontarlo con la stessa logica scritta a mano: un ciclo for e un Vec vuoto in cui fai push. Non è sbagliato, ma la versione concatenata si legge quasi come «filtra, poi moltiplica, poi raccogli», cioè proprio la sequenza di passaggi che vuoi descrivere.
Altri metodi comuni
Sezione intitolata “Altri metodi comuni”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!("somma: {total}, quanti: {count}, c'è un pari: {has_even}");}Output:
somma: 15, quanti: 5, c'è un pari: true.sum() somma tutto, .count() ti dice quanti elementi ci sono e .any() risponde alla domanda «almeno un elemento corrisponde?». Sono solo alcuni dei metodi che ogni iteratore possiede. Ce ne sono molti altri, come enumerate, zip, take e rev. Nella pagina Iteratori personalizzati costruirai un tuo iteratore e vedrai che riceve tutti questi metodi gratis.
Pratica
Sezione intitolata “Pratica”1. Somma dei quadrati dispari
Sezione intitolata “1. Somma dei quadrati dispari”Prendi i numeri da 1 a 10, tieni solo quelli dispari, eleva ognuno al quadrato e sommali tutti. Fallo con un’unica catena di iteratori, senza variabili mut. Suggerimento: concatena .filter(), .map() e .sum(), e dai al risultato un tipo, per esempio let total: i32.
Mostra la soluzione
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) // tieni 1, 3, 5, 7, 9 .map(|n| n * n) // 1, 9, 25, 49, 81 .sum();
println!("somma dei quadrati dispari: {total}");}Output:
somma dei quadrati dispari: 165Rust ha bisogno di : i32 su total perché .sum() può sommare in diversi tipi numerici e non può indovinare quale vuoi.
2. Urla le parole lunghe
Sezione intitolata “2. Urla le parole lunghe”Parti da vec!["the", "quick", "brown", "fox", "jumps"]. Costruisci un nuovo Vec<String> che contenga solo le parole con più di 3 lettere, in maiuscolo, e stampalo con {:?}. Suggerimento: .filter(|w| w.len() > 3) tiene le parole lunghe e .to_uppercase() trasforma un &str in una String maiuscola. (Le parole restano in inglese perché .len() conta i byte e una lettera accentata ne occupa due.)
Mostra la soluzione
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);}Output:
["QUICK", "BROWN", "JUMPS"]words resta invariato. .iter() lo prende solo in prestito, quindi puoi ancora usare words dopo la catena.
Riepilogo
Sezione intitolata “Riepilogo”- Un iteratore produce valori uno alla volta tramite
.next(), che restituisce unOption. - Dietro i cicli
forci sono gli iteratori. .map()trasforma ogni elemento;.filter()tiene solo quelli che soddisfano una condizione;.collect()trasforma il risultato di nuovo in una collezione concreta.- Concatenare i metodi degli iteratori di solito è più chiaro di un ciclo manuale con un accumulatore mutabile.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Che cosa restituisce .next() su un iteratore?
2. Che cosa fa .collect() alla fine di una catena come .iter().map(...).collect()?
3. In .filter(|n| **n % 2 == 0), perché n viene dereferenziato due volte?
4. Che cosa fa funzionare un ciclo for quando scorre una collezione?
5. Che cosa tiene .filter() nell'iteratore risultante?
Punteggio: 0 / 5
Per approfondire
Sezione intitolata “Per approfondire”- Processing a Series of Items with Iterators — The Rust Book (in inglese): l’introduzione ufficiale a
next,map,filterecollect, con altri esempi. - Comparing Performance: Loops vs. Iterators — The Rust Book (in inglese): perché una catena di iteratori è veloce quanto un ciclo scritto a mano.
- Il trait
Iterator— documentazione della libreria standard (in inglese): l’elenco completo dei metodi degli iteratori, ognuno con un piccolo esempio. Vale la pena sfogliarlo. - Iterators — Rust by Example (in inglese): un esempio breve ed eseguibile che costruisce un iteratore di Fibonacci.