Attualmente Rust fornisce tre costrutti per eseguire attività iterative:
loop
, while
e for
. Ogni costrutto serve a scopi diversi.
Il costrutto loop
("ciclo") è la forma più semplice di ciclo disponibile
in Rust. Usando la parola-chiave loop
, Rust fornisce un modo
di ciclare indefinitamente finché si raggiunge qualche istruzione
di terminazione. Il ciclo infinito di Rust è fatto così:
loop { println!("Cicla per sempre!"); }
Rust ha anche un ciclo while
("fintanto che"). È fatto così:
let mut x = 5; // mut x: i32 let mut fatto = false; // mut fatto: bool while !fatto { x += x - 3; println!("{}", x); if x % 5 == 0 { fatto = true; } }
I cicli while
sono la scelta appropriata quando non si è sicuri
di quante volte si dovrà ciclare.
Se serve un ciclo infinito, si può essere tentati di scrivere:
fn main() { while true { }while true {
Tuttavia, il costrutto loop
è molto più adatto per gestire questo caso:
loop {
L'analisi del flusso di costrullo di Rust tratta questo costrutto
diversamente da un while true
, dato che sappiamo che ciclerà per sempre.
In generale, più informazione possiamo dare al compilatore, meglio può fare
con la sicurezza e la generazione del codice, e perciò si dovrebbe sempre
preferire loop
quando si intende ciclare indefinitamente.
Il ciclo for
viene usato per ciclare un particolare numero di volte.
Però i cicli for
di Rust funzionano un po' diversamente dagli altri
linguaggi di sistema. Il ciclo for
di Rust non somiglia al ciclo for
del linguaggio C:
for (x = 0; x < 10; x++) {
printf( "%d\n", x );
}
Invece, è fatto così:
fn main() { for x in 0..10 { println!("{}", x); // x: i32 } }for x in 0..10 { println!("{}", x); // x: i32 }
o, in termini più astratti,
fn main() { for var in expression { code } }for var in expression { code }
L'espressione è un elemento che può essere convertito in un [iteratore] usando
IntoIterator
. L'iteratore rende una serie di elementi. Ogni elemento è
un'iterazione del ciclo. Tale valore viene poi associato al nome var
,
che è valido solo nel corpo del ciclo. Una volta che il corpo è finito,
il prossimo valore viene preso dall'iteratore, e si esegue un'altra iterazione.
Quando non ci sono più valori, il ciclo for
è finito.
Nel nostro esempio, 0..10
è un'espressione che prende una posizione
di inizio e una di fine, e dà un iteratore su quei valori. Tuttavia, il limite
superiore è escluso, così che questo ciclo stamperà i numeri da 0
a 9
,
e non il 10
.
Rust non ha il ciclo for
in "stile C" di proposito. Controllare manualmente
ogni elemento del ciclo è complicato e soggetto a errori, anche per
sviluppatori esperti nel linguaggio C.
Quando c'è bisogno di tener traccia di quante volte si ha già ciclato,
si può usare la funzione .enumerate()
.
for (i, j) in (5..10).enumerate() { println!("i = {} e j = {}", i, j); }
Emette:
i = 0 e j = 5
i = 1 e j = 6
i = 2 e j = 7
i = 3 e j = 8
i = 4 e j = 9
In questo caso si devono aggiungere le parentesi intorno al range.
let linee = "ciao\nmondo".lines(); for (numerolinea, linea) in linee.enumerate() { println!("{}: {}", numerolinea, linea); }
Emette:
0: ciao
1: mondo
Diamo un'occhiata a quel ciclo while
di prima:
let mut x = 5; let mut fatto = false; while !fatto { x += x - 3; println!("{}", x); if x % 5 == 0 { fatto = true; } }
Abbiamo dovuto tenere apposta una variabile booleana, fatto
, per sapere
quando dovremmo uscire dal ciclo. Rust ha due parole-chiave per aiutarci
a modificare le iterazioni: break
("interrompi") e continue
("continua").
In questo caso, possiamo scrivere il ciclo in un modo migliore usando break
:
let mut x = 5; loop { x += x - 3; println!("{}", x); if x % 5 == 0 { break; } }
Adesso cicliamo per sempre usando loop
e usiamo break
per uscire
precocemente. Anche eseguire un'istruzione return
potrebbe servire
a terminare il ciclo precocemente.
continue
è simile, ma invece di terminare il ciclo, passa alla prossima
iterazione. Questo codice stamperà solamente i numeri dispari:
for x in 0..10 { if x % 2 == 0 { continue; } println!("{}", x); }
Si potrebbero anche incontrare situazioni in cui ci sono cicli annidati
si vuole specificare a quale ciclo si riferisce una particolare istruzione
break
o continue
. Come nella maggior parte degli altri linguaggi,
di default an'istruzione break
o continue
si applicheranno al ciclo
più interno. Dove si volesse applicare break
o continue
a uno dei cicli
esterni, si possono usare delle etichette. Il seguente codice stamperà
solamente quando sia x
che y
sono dispari:
'esterno: for x in 0..10 { 'interno: for y in 0..10 { if x % 2 == 0 { continue 'esterno; } // continua al ciclo su x if y % 2 == 0 { continue 'interno; } // continua al ciclo su y println!("x: {}, y: {}", x, y); } }