00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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.