Salta ai contenuti

If let e while let in Rust

Hai usato match per gestire Option e Result. Il più delle volte, però, ti interessa davvero un solo pattern e vuoi ignorare tutto il resto. Scrivere un match completo con un ramo _ => {} solo per non fare niente funziona, ma è più cerimonia di quanta ne serva. if let è la scorciatoia.

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("L'età è {value}"),
_ => {}, // questo caso in realtà non ci interessa
}
}

Quel _ => {} non fa altro che soddisfare la regola del match per cui «ogni caso deve essere gestito». Se vuoi solo agire su Some, è più codice di quanto l’idea meriti.

fn main() {
let age: Option<i32> = Some(30);
if let Some(value) = age {
println!("L'età è {value}");
}
}

Output:

L'età è 30
▶ Provalo tu

Leggilo così: «se age corrisponde al pattern Some(value), esegui questo blocco, con value legato a quello che c’era dentro». Se age è None, il blocco viene semplicemente saltato: nessun errore, niente stampato.

if let può avere un else, proprio come un normale if:

fn main() {
let age: Option<i32> = None;
if let Some(value) = age {
println!("L'età è {value}");
} else {
println!("Nessuna età indicata");
}
}
▶ Provalo tu

Funziona allo stesso modo con Result, che hai conosciuto nella pagina Result:

fn main() {
let input = "42";
if let Ok(number) = input.parse::<i32>() {
println!("Convertito: {number}");
} else {
println!("Impossibile convertirlo");
}
}
▶ Provalo tu

Proprio come if ha else if, if let può essere seguito da else if let. Rust prova ogni pattern a turno ed esegue il primo blocco che corrisponde. È comodo quando hai più posti in cui cercare un valore, in ordine di preferenza:

fn main() {
let nickname: Option<&str> = None;
let full_name: Option<&str> = Some("Ferris Crab");
if let Some(nick) = nickname {
println!("Ciao, {nick}!");
} else if let Some(name) = full_name {
println!("Buongiorno, {name}.");
} else {
println!("Buongiorno, sconosciuto.");
}
}

Output:

Buongiorno, Ferris Crab.
▶ Provalo tu

nickname è None, quindi il primo blocco viene saltato. full_name corrisponde a Some(name), quindi viene eseguito quel blocco, e l’else finale non viene mai raggiunto. Puoi anche inserire una condizione normale, come else if is_admin { ... }, in qualsiasi punto della catena.

Ogni variabile esiste solo dentro il proprio blocco: nick non si può usare nel secondo blocco, e name non si può usare dopo la fine della catena. Se ti serve il valore spacchettato per tutto il resto del codice, guarda let-else nella pagina Pattern matching.

while let applica la stessa idea a un ciclo: continua a girare finché un valore corrisponde a un pattern. È particolarmente comodo per svuotare una collezione:

fn main() {
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{top}");
}
}

Output:

3
2
1
▶ Provalo tu

.pop() restituisce Some(valore) finché c’è ancora qualcosa, e None quando il vettore è vuoto: a quel punto while let ferma il ciclo da solo. Non serve controllare la lunghezza a mano.

Usa match quando hai davvero bisogno di gestire più casi in modi diversi. Passa a if let quando c’è un solo pattern che ti interessa e tutto il resto va semplicemente ignorato. Non sostituisce match: è solo un modo più breve di scrivere il caso comune «mi interessa un solo ramo».

Parti da let mut numbers = vec![4, 8, 15]; e da un totale progressivo total uguale a 0. Usa while let con .pop() per togliere i numeri dalla fine uno alla volta, aggiungere ciascuno a total, e stampare il numero e il nuovo totale. Suggerimento: total ha bisogno di mut, e total += n ci aggiunge un valore.

Mostra la soluzione
fn main() {
let mut numbers = vec![4, 8, 15];
let mut total = 0;
while let Some(n) = numbers.pop() {
total += n;
println!("preso {n}, ora il totale è {total}");
}
}

Output:

preso 15, ora il totale è 15
preso 8, ora il totale è 23
preso 4, ora il totale è 27
▶ Provalo tu

.pop() prende dalla fine, ed è per questo che il 15 esce per primo.

Salva let input = "17";. Se si può convertire in un u32, stampa se quell’età è da maggiorenne (18 o più) o da minorenne. Se la conversione non riesce, stampa che non è un numero. Prova anche a cambiare input in "abc". Suggerimento: metti un normale if / else dentro il blocco if let.

Mostra la soluzione
fn main() {
let input = "17";
if let Ok(age) = input.parse::<u32>() {
if age >= 18 {
println!("{age}: maggiorenne");
} else {
println!("{age}: minorenne");
}
} else {
println!("'{input}' non è un numero");
}
}

Output:

17: minorenne
▶ Provalo tu

Con input = "abc", la conversione fallisce, viene eseguito l’else esterno e il programma stampa 'abc' non è un numero.

  • if let Pattern = valore { ... } esegue il blocco solo se valore corrisponde a Pattern: tutto il resto viene saltato in silenzio.
  • Aggiungi else per gestire il caso che non corrisponde, come in un normale if, e concatena più pattern con else if let.
  • while let Pattern = valore { ... } continua a girare finché il valore corrisponde, utile per svuotare una collezione con .pop().
  • Usa if let/while let quando ti interessa un solo pattern; usa match quando devi gestire più casi in modo distinto.

Verifica veloce

1. Cosa succede quando viene eseguito if let Some(value) = age e age è None?

2. Quando conviene usare match invece di if let?

3. In while let Some(top) = stack.pop() { ... }, quando si ferma il ciclo?

Punteggio: 0 / 3