Ciclo For in Rust
Il ciclo for in Rust è lo strumento principale per iterare su sequenze di valori. A differenza del classico for con tre espressioni presente in C o Java, il for di Rust utilizza il pattern for-in e lavora con qualsiasi tipo che implementa il trait IntoIterator. Questo lo rende sicuro, espressivo e impossibile da far uscire dai limiti di un array.
Range con … e …=
Il modo più semplice di usare un for è iterare su un range di numeri.
fn main() {
// Range esclusivo: da 0 a 4 (5 escluso)
for i in 0..5 {
println!("i = {i}");
}
}
Il range .. è esclusivo (l’estremo superiore non è incluso). Per un range inclusivo, si usa ..=:
fn main() {
// Range inclusivo: da 1 a 5 (5 incluso)
for i in 1..=5 {
println!("i = {i}");
}
}
Si possono anche creare range decrescenti usando .rev():
fn main() {
for i in (1..=10).rev() {
println!("Conto alla rovescia: {i}");
}
}
Iterare su collezioni
Il for si usa comunemente per iterare su vettori, array e altre collezioni.
fn main() {
let frutti = ["mela", "pera", "banana", "arancia"];
for frutto in frutti {
println!("Mi piace la {frutto}");
}
}
Con i vettori:
fn main() {
let numeri = vec![10, 20, 30, 40, 50];
for n in &numeri {
println!("Numero: {n}");
}
// numeri è ancora utilizzabile perché abbiamo iterato per riferimento
println!("Totale elementi: {}", numeri.len());
}
Nota l’uso di &numeri: senza il riferimento, il vettore verrebbe consumato (moved) e non sarebbe più utilizzabile dopo il ciclo.
iter(), into_iter() e iter_mut()
Rust offre tre modi per creare un iteratore da una collezione, ognuno con semantica diversa riguardo all’ownership.
iter() - Riferimenti immutabili
iter() crea un iteratore che presta ogni elemento in sola lettura (&T).
fn main() {
let nomi = vec!["Alice", "Bob", "Carlo"];
for nome in nomi.iter() {
println!("Ciao, {nome}!");
}
// nomi è ancora accessibile
println!("Numero di nomi: {}", nomi.len());
}
into_iter() - Consumo della collezione
into_iter() consuma la collezione e restituisce gli elementi per valore (T). Dopo il ciclo, la collezione originale non è più disponibile.
fn main() {
let nomi = vec![String::from("Alice"), String::from("Bob")];
for nome in nomi.into_iter() {
println!("Nome: {nome}");
}
// Errore! nomi è stato consumato:
// println!("{:?}", nomi);
}
Nota: scrivere for x in collezione equivale a for x in collezione.into_iter().
iter_mut() - Riferimenti mutabili
iter_mut() crea un iteratore con riferimenti mutabili (&mut T), permettendo di modificare gli elementi durante l’iterazione.
fn main() {
let mut prezzi = vec![10.0, 20.0, 30.0, 40.0];
for prezzo in prezzi.iter_mut() {
*prezzo *= 1.22; // Applica IVA del 22%
}
println!("Prezzi con IVA: {:?}", prezzi);
}
Enumerate
Il metodo enumerate() aggiunge un indice a ogni elemento iterato, restituendo tuple (indice, valore).
fn main() {
let colori = vec!["rosso", "verde", "blu", "giallo"];
for (i, colore) in colori.iter().enumerate() {
println!("{i}: {colore}");
}
}
Output:
0: rosso
1: verde
2: blu
3: giallo
enumerate() è preferibile rispetto a un contatore manuale perché è più idiomatico e meno soggetto a errori.
Zip
Il metodo zip() combina due iteratori in uno solo, producendo tuple di elementi appaiati. L’iterazione si ferma quando uno dei due iteratori finisce.
fn main() {
let nomi = vec!["Alice", "Bob", "Carlo"];
let punteggi = vec![95, 87, 92];
for (nome, punteggio) in nomi.iter().zip(punteggi.iter()) {
println!("{nome}: {punteggio} punti");
}
}
Si possono concatenare anche più di due iteratori:
fn main() {
let a = [1, 2, 3];
let b = [10, 20, 30];
let c = [100, 200, 300];
for ((x, y), z) in a.iter().zip(b.iter()).zip(c.iter()) {
println!("{x} + {y} + {z} = {}", x + y + z);
}
}
Iterare su stringhe
Per iterare sui caratteri di una stringa si usa .chars():
fn main() {
let parola = "Ciao Rust!";
for (i, c) in parola.chars().enumerate() {
println!("Carattere {i}: '{c}'");
}
}
Pattern matching nel for
Il for supporta la destrutturazione direttamente nel pattern:
fn main() {
let coordinate = vec![(1, 2), (3, 4), (5, 6)];
for (x, y) in &coordinate {
println!("x: {x}, y: {y}");
}
}
Conclusione
Il ciclo for è il costrutto di iterazione preferito in Rust grazie alla sua sicurezza e chiarezza. L’uso di iter(), into_iter() e iter_mut() offre un controllo preciso sull’ownership durante l’iterazione. Metodi come enumerate() e zip() rendono le operazioni su collezioni concise e leggibili. Quando possibile, preferisci for a while per iterare su sequenze note.