SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Utilizzo dei Pattern in Rust
I pattern sono utilizzati in diversi contesti in Rust, e li hai usati molto senza accorgertene! In questa sezione parleremo di tutti i luoghi dove i pattern sono validi.
Bracci di match
Come discusso nel Capitolo 6, usiamo i pattern nei bracci delle espressioni di match. Formalmente, le espressioni di match sono definite dalla parola chiave match
, un valore su cui fare il match, e uno o più bracci di match che consistono in un pattern e un’espressione da eseguire se il valore fa match con quel pattern, così:
rust
match VALORE {
PATTERN => ESPRESSIONE,
PATTERN => ESPRESSIONE,
PATTERN => ESPRESSIONE,
}
Ad esempio, ecco un’espressione di match dal Listato 6-5 che fa match su un valore di tipo Option<i32>
nella variabile x
:
rust
match x {
None => None,
Some(i) => Some(i + 1),
}
I pattern in questa espressione di match sono None
e Some(i)
a sinistra di ogni freccia.
Un requisito per le espressioni di match è che devono essere esaustive nel senso che tutte le possibilità per il valore nell’espressione di match devono essere considerate. Un modo per assicurarsi di aver coperto ogni possibilità è avere un pattern “catchall” per l’ultimo braccio: ad esempio, un nome di variabile che fa match con qualsiasi valore può coprire tutti i casi rimanenti.
Il particolare pattern _
farà match con qualsiasi cosa, ma non si legherà mai a una variabile, quindi è spesso usato nell’ultimo braccio di match. Il pattern _
può essere utile quando si desidera ignorare qualsiasi valore non specificato, ad esempio.
Espressioni condizionali if let
Nel Capitolo 6 abbiamo discusso come usare le espressioni if let
principalmente come un modo più breve per scrivere l’equivalente di un match che fa match solo con un caso. Opzionalmente, if let
può avere un else
corrispondente contenente del codice da eseguire se il pattern nell’if let
non fa match.
Il Listato 18-1 mostra che è anche possibile mescolare e abbinare espressioni if let
, else if
e else if let
. Farlo ci dà più flessibilità rispetto a un’espressione di match, in cui possiamo confrontare solo un valore con i pattern.
Il codice nel Listato 18-1 determina quale colore dare allo sfondo in base a una serie di controlli per diverse condizioni.
Se l’utente specifica un colore preferito, quel colore è usato come sfondo. Se non viene specificato alcun colore preferito e oggi è martedì, lo sfondo è verde. Altrimenti, se l’utente specifica la propria età come una stringa e riusciamo a convertirla con successo in un numero, il colore è viola o arancione a seconda del valore del numero. Se nessuna di queste condizioni si applica, lo sfondo è blu.
Questa struttura condizionale ci consente di supportare requisiti complessi. Con i valori hard-coded che abbiamo qui, questo esempio stamperà “Using purple as the background color”.
Si può notare che if let
può anche introdurre variabili ombreggiate allo stesso modo dei bracci di match: la linea if let Ok(age) = age
introduce una nuova variabile ombreggiata age
che contiene il valore all’interno della variante Ok
. Questo significa che dobbiamo inserire la condizione if age > 30
all’interno di quel blocco: non possiamo combinare queste due condizioni in if let Ok(age) = age && age > 30
. L’età ombreggiata che vogliamo confrontare con 30 non è valida fino a quando non inizia il nuovo ambito con la graffa.
Lo svantaggio nell’utilizzare le espressioni if let
è che il compilatore non controlla l’esauribilità, mentre con le espressioni di match lo fa. Se omettessimo l’ultimo blocco else
e quindi non gestissimo alcuni casi, il compilatore non ci avviserebbe del possibile bug logico.
Cicli Condizionali while let
Simile nella struttura all’if let
, il ciclo condizionale while let
consente a un ciclo while
di continuare ad eseguirsi finché un pattern continua a fare match. Nel Listato 18-2 scriviamo un ciclo while let
che utilizza un vettore come stack e stampa i valori nel vettore nell’ordine opposto in cui sono stati inseriti.
rust
let mut stack = Vec::new();
stack.push(1);
stack.push(2);
stack.push(3);
while let Some(top) = stack.pop() {
println!("{}", top);
}
Questo esempio stampa 3, 2 e poi 1. Il metodo pop
prende l’ultimo elemento dal vettore e restituisce Some(value)
. Se il vettore è vuoto, pop
restituisce None
. Il ciclo while
continua a eseguire il codice nel suo blocco fintanto che pop
restituisce Some
. Quando pop
restituisce None
, il ciclo si ferma. Possiamo usare while let
per rimuovere ogni elemento dallo stack.
Cicli for
In un ciclo for
, il valore che segue direttamente la parola chiave for
è un pattern. Ad esempio, in for x in y
il x
è il pattern. Il Listato 18-3 mostra come utilizzare un pattern in un ciclo for
per destrutturare, o spezzare, una tupla come parte del ciclo for
.
rust
let v = vec!['a', 'b', 'c'];
for (index, value) in v.iter().enumerate() {
println!("{} is at index {}", value, index);
}
Il codice nel Listato 18-3 stamperà:
csharp
a is at index 0
b is at index 1
c is at index 2
Adattiamo un iteratore utilizzando il metodo enumerate
in modo che produca un valore e l’indice per quel valore, inserito in una tupla. Il primo valore prodotto è la tupla (0, ‘a’). Quando questo valore fa match con il pattern (index, value)
, index
sarà 0 e value
sarà ‘a’, stampando la prima riga dell’output.
Dichiarazioni let
Prima di questo capitolo, avevamo discusso esplicitamente l’utilizzo di pattern con match
e if let
, ma in realtà abbiamo utilizzato pattern anche in altri contesti, inclusi nelle dichiarazioni let
. Ad esempio, considera questa assegnazione di variabile diretta con let
:
rust
let x = 5;
Ogni volta che hai usato una dichiarazione let
come questa hai usato pattern, anche se potresti non essertene accorto! Più formalmente, una dichiarazione let
appare così:
rust
let PATTERN = ESPRESSIONE;
In dichiarazioni come let x = 5;
con un nome di variabile nel posto del pattern, il nome della variabile è solo una forma particolarmente semplice di un pattern. Rust confronta l’espressione con il pattern e assegna eventuali nomi che trova. Quindi, nell’esempio let x = 5;
, x
è un pattern che significa “lega ciò che fa match qui alla variabile x
“. Poiché il nome x
è l’intero pattern, questo pattern significa effettivamente “lega tutto alla variabile x
, qualunque sia il valore”.
Per vedere più chiaramente l’aspetto del matching dei pattern di let
, considera il Listato 18-4, che utilizza un pattern con let
per destrutturare una tupla.
rust
let (x, y, z) = (1, 2, 3);
Qui, facciamo match di una tupla con un pattern. Rust confronta il valore (1, 2, 3) con il pattern (x, y, z)
e vede che il valore fa match con il pattern, quindi Rust lega 1 a x
, 2 a y
e 3 a z
. Puoi pensare a questo pattern di tupla come a tre pattern di variabili individuali nidificati al suo interno.