|
Le iterazioni introdotte dal for sono decisamente più varie di quelle viste finora. Anche la sintassi quindi cambia a seconda della modalità con la quale intendiamo utilizzare questo costrutto. La sua versatilità lo rende tra gli strumenti più utili e certamente lo userete molto spesso nei vostri programmi. Di base la sintassi è la seguente: for variabile in espressione { istruzioni } Un concetto che si accompagna spesso il nostro for è quello di range ai quali accenniamo brevemente, ad essi dedicheremo comunque un paragrafo specifico. Un range tipicamente ha il formato start..end che equivale, supponendo di ispezionare tale range con la variabile x a: start <= x < end quindi escluso il valore più alto. Se vogliamo che anche questo sia incluso la sintassi diventa: start ..= end possiamo quindi partire con il primo semplice esempio:
Ricordiamo che la variabile contrassegnata con il segnaposto _ ha un valore che può essere ignorato ed evita il warning da parte del compilatore. Questo programma espone a video la stringa "ciao" (senza apici, ovviamente) per 5 volte (da 0 compreso a 4). Va detto che se al posto di _ mettessimo una x questa non sarebbe utilizzabile esternamente al ciclo, per le solite ragioni di ambito che dovrebbero esservi chiare. Anche se non direttamente collegato in maniera peculiare all'istruzione for bisogna fare un po' di attenzione perchè le regole dell'ownership sono sempre in agguato e visto che le iterazioni su range sono il pane quotidiano vale la pena perderci qualche minuto. Vediamo l'esempio seguente:
Questo programma non compila e in messagio che riceviamo, in qualche modo, chiarisce dove sta l'errore: error[E0382]: borrow of moved value: `r1` --> r285.rs:8:20 | 3 | let r1 = 0..10; | -- move occurs because `r1` has type `std::ops::Range<i32>`, which does not implement the `Copy` trait 4 | for x in r1 | -- `r1` moved due to this implicit call to `.into_iter()` ... 8 | println!("{}", r1.len()); | ^^ value borrowed here after move | note: `into_iter` takes ownership of the receiver `self`, which moves `r1` --> /rustc/25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04\library\core\src\iter\traits\collect.rs:268:18 error: aborting due to 1 previous error come vedremo quando affronteremo i range, essi non implementano il trait copy quindi durante l'iterazione into_inter, che è il metodo che si occupa di eseguire l'iterazione, compie una operazione di ownership, di fatto trascinando il range all'interno del ciclo iniziato alla riga 4. Quando questo ciclo termina, il drop coinvolge anche il range, che a questo punto si trova all'interno, quindi tale range non è più disponbile all'esterno. La soluzione può essere solo una modifica della gestione del range, ad esempio: for x in r1.clone() e tutto gira perfettamente. Continuando con il nostro esempio, vediamo nel prossimo spezzone di codice come implementare:
Detto questo vediamo altri esempi di uso pratico del nostro for. Un caso classico è l'iterazione con i vettori (vedi capitolo per approfondire i vettori): let numbers = vec![1, 2, 3, 4, 5]; for num in numbers { println!("{}", num); } questo codice si commenta da solo direi. Interessante è il seguente che ci mostra anche come ricorrendo ad una ennupla si possa lavorare su più di un indice: let pairs = [(1, "uno"), (2, "due"), (3, "tre")]; for (number, name) in pairs.iter() { println!("{}: {}", number, name); } che di darà come output:
Sempre utilizzando una ennupla vediamo un semplice sistema per tenere traccia delle iterazioni e che fa uso del metodo enumerate(). Vediamo come funziona modificando il primo esempio del paragrafo.
Un altro esempio che può tornare utile è il seguente che prevede una semplice interazione tra due variabili all'interno del ciclo aperto da for. Per l'occasione fa la sua comparsa il metodo zip che è utilizzato per combinare gli elementi di due iteratori in un singolo iteratore che restituisce ennuple. Ogni tupla è composta da un elemento di ciascun iteratore originale. Questo è particolarmente utile quando si desidera lavorare con gli elementi di due sequenze in parallelo. Ecco un esempio banale:
Con il seguente output:
Una cosa interessante e a volte poco nota è la possibilità di usare delle etichette con break e continue in presenza di cicli annidati. Le etichette seguono la sintassi: 'nome-etichetta punto di richiamo dell'etichetta 'nome-etichetta: punto di arrivo dell'etichetta Se pensate che questa possibilità avvicini il classico e famigerato goto presente in altri linguaggi, non è questo il caso, vuoi per l'ambito limitato ai soli cicli for while e loop, vuoi per il controllo a cui il compilatore sottopone l'uso di queste etichette. Vediamo quindi un esempio:
Alla riga 6 abbiamo un salto che fa si che continue salti alla successiva iterazione indicata alla riga 2. Vi lascio il divertimento di controllare cosa succede eliminando le due etichette e confrontando quindi l'output nei due casi. |