SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Sintassi dei Pattern
In questa sezione, esaminiamo tutta la sintassi valida nei pattern e discutiamo perché e quando potresti voler usare ognuno di essi.
Corrispondenza con Letterali
Come hai visto nel Capitolo 6, puoi fare corrispondenza tra pattern e letterali direttamente. Il seguente codice fornisce alcuni esempi:
rust
let x = 1;
match x {
1 => println!("uno"),
2 => println!("due"),
3 => println!("tre"),
_ => println!("qualsiasi cosa"),
}
Questo codice stampa uno perché il valore in x è 1. Questa sintassi è utile quando vuoi che il tuo codice esegua un’azione se riceve un valore concreto specifico.
Corrispondenza con Variabili Nominative
Le variabili nominative sono pattern irrefutabili che fanno match con qualsiasi valore, e le abbiamo usate molte volte nel libro. Tuttavia, c’è una complicazione quando si usano variabili nominative nelle espressioni match. Poiché match avvia un nuovo scope, le variabili dichiarate come parte di un pattern all’interno dell’espressione match oscureranno quelle con lo stesso nome al di fuori del costrutto match, come accade con tutte le variabili. Nel Listato 18-11, dichiariamo una variabile chiamata x con il valore Some(5) e una variabile y con il valore 10. Quindi creiamo un’espressione match sul valore x. Guarda i pattern nelle braccia del match e il println! alla fine, e cerca di capire cosa stamperà il codice prima di eseguire questo codice o di leggere oltre.
rust
let x = Some(5);
let y = 10; match x {
Some(50) => println!("Ricevuto 50"),
Some(y) => println!("Fatto match, y = {y}"),
_ => println!("Caso predefinito, x = {:?}", x),
}
println!("Alla fine: x = {:?}, y = {y}", x);
Listato 18-11: Un’espressione match con un braccio che introduce una variabile oscurata y
Diamo un’occhiata a cosa succede quando l’espressione match viene eseguita. Il pattern nella prima braccio del match non fa match con il valore definito di x, quindi il codice continua.
Il pattern nella seconda braccio del match introduce una nuova variabile chiamata y che farà match con qualsiasi valore all’interno di un valore Some. Poiché siamo in un nuovo scope all’interno dell’espressione match, questa è una nuova variabile y, non quella che abbiamo dichiarato all’inizio con il valore 10. Questo nuovo binding y farà match con qualsiasi valore all’interno di un Some, che è quello che abbiamo in x. Quindi, questo nuovo y si lega al valore interno del Some in x. Quel valore è 5, quindi l’espressione per quel braccio viene eseguita e stampa Fatto match, y = 5.
Se x fosse stato un valore None invece di Some(5), i pattern nei primi due bracci non avrebbero fatto match, quindi il valore avrebbe fatto match con il trattino basso. Non abbiamo introdotto la variabile x nel pattern del braccio del trattino basso, quindi la x nell’espressione è ancora la x esterna che non è stata oscurata. In questo caso ipotetico, il match stamperebbe Caso predefinito, x = None.
Quando l’espressione match è terminata, il suo scope finisce, e così lo scope del y interno. L’ultimo println! produce Alla fine: x = Some(5), y = 10.
Per creare un’espressione match che confronta i valori della x esterna e y, anziché introdurre una variabile oscurata, dovremmo utilizzare invece una condizione di guardia match. Parleremo delle condizioni di guardia match più avanti nella sezione “Condizioni Extra con Guardie di Match”.
Pattern Multipli
Nelle espressioni match, puoi fare match con più pattern utilizzando la sintassi |, che è l’operatore o del pattern. Ad esempio, nel seguente codice facciamo match con il valore di x contro i bracci del match, il primo dei quali ha un’opzione o, il che significa che se il valore di x fa match con uno dei valori in quel braccio, verrà eseguito il codice di quel braccio:
rust
let x = 1;
match x {
1 | 2 => println!("uno o due"),
3 => println!("tre"),
_ => println!("qualsiasi cosa"),
}
Questo codice stampa uno o due.
Corrispondenza con Range di Valori con ..=
La sintassi ..= ci permette di fare match con un intervallo inclusivo di valori. Nel codice seguente, quando un pattern fa match con uno qualsiasi dei valori all’interno dell’intervallo dato, verrà eseguito quel braccio:
rust
let x = 5;
match x {
1..=5 => println!("uno attraverso cinque"),
_ => println!("altro"),
}
Se x è 1, 2, 3, 4 o 5, il primo braccio farà match. Questa sintassi è più conveniente per i multipli valori di match rispetto all’uso dell’operatore | per esprimere la stessa idea; se dovessimo usare | dovremmo specificare 1 | 2 | 3 | 4 | 5. Specificare un intervallo è molto più breve, specialmente se vogliamo fare match, ad esempio, con qualsiasi numero tra 1 e 1.000!
Il compilatore verifica che l’intervallo non sia vuoto a tempo di compilazione, e poiché i tipi per i quali Rust può dire se un intervallo è vuoto o meno sono solo i valori char e numerici, gli intervalli sono consentiti solo con valori numerici o char.
Ecco un esempio che utilizza intervalli di valori char:
rust
let x = 'c';
match x {
'a'..='j' => println!("lettera ASCII iniziale"),
'k'..='z' => println!("lettera ASCII tardiva"),
_ => println!("altro"),
}
Rust può dire che ‘c’ è all’interno dell’intervallo del primo pattern e stampa lettera ASCII iniziale.
Decostruzione per Separare i Valori
Possiamo anche usare i pattern per destrutturare strutture, enumerazioni e tuple per utilizzare parti diverse di questi valori. Vediamo ogni caso uno per uno.
Decostruzione di Strutture
Nel Listato 18-12 viene mostrata una struct Punto con due campi, x e y, che possiamo separare usando un pattern con un’istruzione let.
rust
struct Punto {
x: i32,
y: i32,
} fn main() {
let p = Punto { x: 0, y: 7 };
let Punto { x: a, y: b } = p;
assert_eq!(0, a);
assert_eq!(7, b);
}
Questo codice crea le variabili a e b che corrispondono ai valori dei campi x e y della struct p. Questo esempio mostra che i nomi delle variabili nel pattern non devono corrispondere ai nomi dei campi della struct. Tuttavia, è comune abbinare i nomi delle variabili ai nomi dei campi per rendere più facile ricordare quali variabili provengono da quali campi. A causa di questo uso comune, e perché scrivere let Punto { x: x, y: y } = p; contiene molta duplicazione, Rust ha una scorciatoia per i pattern che corrispondono ai campi di una struct: è sufficiente elencare il nome del campo della struct, e le variabili create dal pattern avranno gli stessi nomi. Il Listato 18-13 si comporta allo stesso modo del codice nel Listato 18-12, ma le variabili create nel pattern let sono x e y invece di a e b.
rust
struct Punto {
x: i32,
y: i32,
} fn main() {
let p = Punto { x: 0, y: 7 };
let Punto { x, y } = p;
assert_eq!(0, x);
assert_eq!(7, y);
}
Questo codice crea le variabili x e y che corrispondono ai campi x e y della variabile p. Il risultato è che le variabili x e y contengono i valori della struct p.
Possiamo anche destrutturare con valori letterali come parte del pattern della struct invece di creare variabili per tutti i campi. Farlo ci consente di testare alcuni dei campi per valori particolari mentre creiamo variabili per destrutturare gli altri campi.
Nel Listato 18-14, abbiamo un’espressione match che separa i valori di Punto in tre casi: punti che giacciono direttamente sull’asse x (che è vero quando y = 0), sull’asse y (x = 0), o nessuno dei due.
rust
fn main() {
let p = Punto { x: 0, y: 7 };
match p {
Punto { x, y: 0 } => println!("Sull'asse x a {x}"),
Punto { x: 0, y } => println!("Sull'asse y a {y}"),
Punto { x, y } => {
println!("Su nessun asse: ({x}, {y})");
}
}
}
Il primo braccio farà match con qualsiasi punto che giace sull’asse x specificando che il campo y fa match se il suo valore corrisponde al letterale 0. Il pattern crea comunque una variabile x che possiamo usare nel codice per questo braccio.
Allo stesso modo, il secondo braccio fa match con qualsiasi punto sull’asse y specificando che il campo x fa match se il suo valore è 0 e crea una variabile y per il valore del campo y. Il terzo braccio non specifica nessun letterale, quindi fa match con qualsiasi altro Punto e crea variabili sia per i campi x che y.
In questo esempio, il valore p fa match con il secondo braccio in virtù di x che contiene un 0, quindi questo codice stamperà Sull’asse y a 7.
Ricorda che un’espressione match smette di controllare i bracci una volta trovato il primo pattern che fa match, quindi anche se Punto { x: 0, y: 0} giace sull’asse x e sull’asse y, questo codice stamperebbe solo Sull’asse x a 0.
Decostruzione di Enumerazioni
Abbiamo destrutturato enumerazioni in questo libro (per esempio, Listato 6-5 nel Capitolo 6), ma non abbiamo ancora discusso esplicitamente che il pattern per destrutturare un’enumerazione corrisponde al modo in cui i dati memorizzati all’interno dell’enumerazione sono definiti. Come esempio, nel Listato 18-15 utilizziamo l’enum Messaggio dal Listato 6-2 e scriviamo un match con pattern che destrutturerà ciascun valore interno.
rust
enum Messaggio {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
} fn main() {
let msg = Messaggio::ChangeColor(0, 160, 255);
match msg {
Messaggio::Quit => {
println!("La variante Quit non ha dati da destrutturare.");
}
Messaggio::Move { x, y } => {
println!("Muovi nella direzione x {x} e nella direzione y {y}");
}
Messaggio::Write(testo) => {
println!("Messaggio di testo: {testo}");
}
Messaggio::ChangeColor(r, g, b) => {
println!("Cambia il colore in rosso {r}, verde {g}, e blu {b}",)
}
}
}
Questo codice stamperà Cambia il colore in rosso 0, verde 160 e blu 255. Prova a cambiare il valore di msg per vedere eseguire il codice dagli altri bracci.
Per varianti di enum senza dati, come Messaggio::Quit, non possiamo destrutturare ulteriormente il valore. Possiamo fare match solo sul valore letterale Messaggio::Quit, e nessuna variabile è in quel pattern.
Per varianti di enum simili a strutture, come Messaggio::Move, possiamo utilizzare un pattern simile a quello che specifichiamo per fare match con le strutture. Dopo il nome della variante, mettiamo parentesi graffe e quindi elenchiamo i campi con variabili in modo da separare le parti da usare nel codice per questo braccio. Qui utilizziamo la forma abbreviata come abbiamo fatto nel Listato 18-13.
Per varianti di enum simili a tuple, come Messaggio::Write che contiene una tupla con un elemento e Messaggio::ChangeColor che contiene una tupla con tre elementi, il pattern è simile a quello che specifichiamo per fare match con le tuple. Il numero di variabili nel pattern deve corrispondere al numero di elementi nella variante con cui facciamo match.
Decostruzione di Strutture ed Enumerazioni Nidificate
Finora, i nostri esempi sono stati tutti di matching con strutture o enumerazioni di un solo livello, ma il matching può funzionare anche su elementi nidificati! Ad esempio, possiamo riscrivere il codice nel Listato 18-15 per supportare colori RGB e HSV nel messaggio ChangeColor, come mostrato nel Listato 18-16.
Decostruzione di Struct e Tuple
Possiamo combinare, abbinare e annidare i pattern di destrutturazione in modi ancora più complessi. L’esempio seguente mostra una destrutturazione complicata in cui annidiamo struct e tuple all’interno di una tupla e destrutturiamo tutti i valori primitivi:
rust
let ((piedi, pollici), Punto { x, y }) = ((3, 10), Punto { x: 3, y: -10 });
Questo codice ci consente di suddividere i tipi complessi nei loro componenti in modo da poter utilizzare separatamente i valori di nostro interesse.
La destrutturazione con i pattern è un modo comodo per utilizzare parti di valori, come il valore di ciascun campo in una struct, separatamente l’uno dall’altro. Ignorare i Valori in un Pattern
Hai visto che a volte è utile ignorare i valori in un pattern, ad esempio nell’ultimo braccio di un match, per ottenere un “catchall” che non fa effettivamente nulla ma considera tutti i valori possibili rimanenti. Ci sono alcuni modi per ignorare interi valori o parti di valori in un pattern: utilizzando il pattern _, utilizzando il pattern _ all’interno di un altro pattern, utilizzando un nome che inizia con un trattino basso o utilizzando .. per ignorare le parti rimanenti di un valore. Esploriamo come e perché utilizzare ciascuno di questi pattern. Ignorare un Intero Valore con _
Abbiamo usato il trattino basso come un pattern jolly che corrisponderà a qualsiasi valore ma non si legherà al valore. Questo è particolarmente utile come ultimo braccio in un’espressione match, ma possiamo anche usarlo in qualsiasi pattern, compresi i parametri della funzione, come mostrato nel Listato 18-17.
rust
fn foo(_: i32, y: i32) {
println!("Questo codice utilizza solo il parametro y: {}", y);
}
fn main() {
foo(3, 4);
}
Questo codice ignorerà completamente il valore 3 passato come primo argomento e stamperà Questo codice utilizza solo il parametro y: 4.
Nella maggior parte dei casi, quando non è più necessario un particolare parametro di funzione, si dovrebbe modificare la firma in modo che non includa il parametro non utilizzato. Ignorare un parametro di funzione può essere particolarmente utile nei casi in cui, ad esempio, si sta implementando un trait e si ha bisogno di una certa firma di tipo ma il corpo della funzione nella propria implementazione non ha bisogno di uno dei parametri. Evita così di ottenere un avviso del compilatore sui parametri di funzione non utilizzati, come accadrebbe se si utilizzasse un nome invece. Ignorare Parti di un Valore con un _ Nidificato
Possiamo anche usare _ all’interno di un altro pattern per ignorare solo parte di un valore, ad esempio, quando vogliamo testare solo parte di un valore ma non abbiamo alcuna utilità per le altre parti nel codice corrispondente che vogliamo eseguire. Il Listato 18-18 mostra il codice responsabile della gestione di un valore dell’impostazione. I requisiti aziendali prevedono che all’utente non sia consentito sovrascrivere una personalizzazione esistente di un’impostazione ma può annullare l’impostazione e fornire un valore se è attualmente non impostato.
rust
let mut valore_impostazione = Some(5);
let nuovo_valore_impostazione = Some(10); match (valore_impostazione, nuovo_valore_impostazione) {
(Some(_), Some(_)) => {
println!("Impossibile sovrascrivere un valore personalizzato esistente");
}
_ => {
valore_impostazione = nuovo_valore_impostazione;
}
}
println!("impostazione è {:?}", valore_impostazione);
Questo codice stamperà Impossibile sovrascrivere un valore personalizzato esistente e poi impostazione è Some(5). Nel primo braccio match, non abbiamo bisogno di corrispondere o utilizzare i valori all’interno di entrambe le varianti Some, ma dobbiamo testare il caso in cui valore_impostazione e nuovo_valore_impostazione siano la variante Some. In tal caso, stampiamo il motivo per non aver modificato valore_impostazione e non viene modificato.
In tutti gli altri casi (se sia valore_impostazione che nuovo_valore_impostazione sono None), espressi dal pattern _ nel secondo braccio, vogliamo consentire a nuovo_valore_impostazione di diventare valore_impostazione.
Possiamo anche utilizzare trattini bassi in più posizioni all’interno di un unico pattern per ignorare valori particolari. Il Listato 18-19 mostra un esempio di ignorare il secondo e il quarto valore in una tupla di cinque elementi.
rust
let numeri = (2, 4, 8, 16, 32);
match numeri {
(primo, _, terzo, _, quinto) => {
println!("Alcuni numeri: {primo}, {terzo}, {quinto}")
}
}
Questo codice stamperà Alcuni numeri: 2, 8, 32, e i valori 4 e 16 verranno ignorati. Ignorare una Variabile Non Utilizzata Iniziando il Suo Nome con _
Se si crea una variabile ma non la si utilizza da nessuna parte, Rust di solito emetterà un avviso perché una variabile non utilizzata potrebbe essere un errore. Tuttavia, a volte è utile poter creare una variabile che non si utilizzerà ancora, ad esempio quando si sta prototipando o si sta iniziando un progetto. In questa situazione, si può dire a Rust di non avvisarti dell’uso della variabile non utilizzata iniziando il nome della variabile con un trattino basso. Nel Listato 18-20, creiamo due variabili non utilizzate, ma quando compiliamo questo codice, dovremmo ricevere un avviso solo per una di esse.
rust
fn main() {
let _x = 5;
let y = 10;
}
Qui otteniamo un avviso riguardo all’uso della variabile y, ma non otteniamo un avviso riguardo a _x.
Nota che c’è una differenza sottile tra usare solo _ e usare un nome che inizia con un trattino basso. La sintassi _x ancora lega il valore alla variabile, mentre _ non lega affatto. Per mostrare un caso in cui questa distinzione è importante, il Listato 18-21 ci fornirà un errore.
rust
[Questo codice non si compila!]
let s = Some(String::from("Ciao!")); if let Some(_s) = s {
println!("trovata una stringa");
}
println!("{:?}", s);
Riceveremo un errore perché il valore s verrà comunque spostato in _s, il che ci impedisce di utilizzare nuovamente s. Tuttavia, utilizzando il trattino basso da solo non si lega mai al valore. Il Listato 18-22 si compilerà senza errori perché s non viene vincolato a nulla.
rust
let s = Some(String::from("Ciao!"));
if let Some(_) = s {
println!("trovata una stringa");
}
println!("{:?}", s);
Questo codice funziona perfettamente perché non vincoliamo mai s a nulla; non viene spostato. Ignorare Parti Rimanenti di un Valore con ..
Con i valori che hanno molte parti, possiamo utilizzare la sintassi .. per utilizzare parti specifiche ed ignorare il resto, evitando la necessità di elencare trattini bassi per ogni valore ignorato. Il pattern .. ignora qualsiasi parte di un valore che non abbiamo corrisposto esplicitamente nel resto del pattern. Nel Listato 18-23, abbiamo una struct Punto che tiene una coordinata nello spazio tridimensionale. Nell’espressione match, vogliamo operare solo sulla coordinata x e ignorare i valori nei campi y e z.
rust
struct Punto {
x: i32,
y: i32,
z: i32,
} let origine = Punto { x: 0, y: 0, z: 0 };
match origine {
Punto { x, .. } => println!("x è {}", x),
}
Elenciamo il valore x e includiamo semplicemente il pattern … Questo è più veloce che dover elencare y: _ e z: _, particolarmente quando stiamo lavorando con struct che hanno molti campi in situazioni in cui solo uno o due campi sono rilevanti.
La sintassi .. si espanderà a quanti valori ne ha bisogno. Il Listato 18-24 mostra come utilizzare .. con una tupla.
rust
fn main() {
let numeri = (2, 4, 8, 16, 32);
match numeri {
(primo, .., ultimo) => {
println!("Alcuni numeri: {primo}, {ultimo}");
}
}
}
In questo codice, il primo e l’ultimo valore vengono corrisposti con primo e ultimo. Il .. corrisponderà ed ignorera tutto nel mezzo.
Tuttavia, l’utilizzo di .. deve essere non ambiguo. Se non è chiaro quali valori sono destinati al matching e quali dovrebbero essere ignorati, Rust ci darà un errore. Il Listato 18-25 mostra un esempio di utilizzo di .. in modo ambiguo, quindi non verrà compilato.
rust
[Questo codice non si compila!]
fn main() {
let numeri = (2, 4, 8, 16, 32);
match numeri {
(.., secondo, ..) => {
println!("Alcuni numeri: {}", secondo)
},
}
}
Quando compiliamo questo esempio, otteniamo questo errore:
$ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error: ..
può essere utilizzato solo una volta per pattern di tuple –> src/main.rs:5:22 | 5 | (.., secondo, ..) => { | — ^^ può essere utilizzato solo una volta per pattern di tuple | | | utilizzato precedentemente qui
error: la compilazione di patterns
non è riuscita a causa di un errore precedente
È impossibile per Rust determinare quanti valori nella tupla ignorare prima di corrispondere un valore con secondo e poi quanti ulteriori valori ignorare successivamente. Questo codice potrebbe significare che vogliamo ignorare 2, vincolare secondo a 4 e poi ignorare 8, 16 e 32; o che vogliamo ignorare 2 e 4, vincolare secondo a 8 e poi ignorare 16 e 32; e così via. Il nome della variabile secondo non significa nulla di speciale per Rust, quindi otteniamo un errore del compilatore perché utilizzare .. in due luoghi in questo modo è ambiguo. Condizioni Extra con Guardie Match
Una guardia match è una condizione aggiuntiva if, specificata dopo il pattern in un braccio match, che deve anche corrispondere affinché tale braccio venga scelto. Le guardie match sono utili per esprimere idee più complesse di quanto consenta da solo un pattern.
La condizione può utilizzare variabili create nel pattern. Il Listato 18-26 mostra un match in cui il primo braccio ha il pattern Some(x) e ha anche una guardia match di if x % 2 == 0 (che sarà vera se il numero è pari).
rust
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("Il numero {} è pari", x),
Some(x) => println!("Il numero {} è dispari", x),
None => (),
}
Questo esempio stamperà Il numero 4 è pari. Quando num viene confrontato con il pattern nel primo braccio, corrisponde, perché Some(4) corrisponde a Some(x). Quindi la guardia match controlla se il resto della divisione di x per 2 è uguale a 0 e poiché lo è, viene selezionato il primo braccio.
Se num fosse stato Some(5) invece, la guardia match nel primo braccio sarebbe stata falsa perché il resto di 5 diviso per 2 è 1, che non è uguale a 0. Rust passerebbe quindi al secondo braccio, che corrisponderebbe perché il secondo braccio non ha una guardia match e quindi corrisponde a qualsiasi variante Some.
Non c’è modo di esprimere la condizione if x % 2 == 0 all’interno di un pattern, quindi la guardia match ci dà la possibilità di esprimere questa logica. Lo svantaggio di questa espressività aggiuntiva è che il compilatore non cerca di verificare l’esauribilità quando sono coinvolti espressioni di guardia match.
Nel Listato 18-11, abbiamo menzionato che potevamo usare le guardie match per risolvere il nostro problema di shadowing del pattern. Ricorda che abbiamo creato una nuova variabile all’interno del pattern nell’espressione match invece di utilizzare la variabile all’esterno del match. Quella nuova variabile significava che non potevamo testare contro il valore della variabile esterna. Il Listato 18-27 mostra come possiamo utilizzare una guardia match per risolvere questo problema.
rust
fn main() {
let x = Some(5);
let y = 10; match x {
Some(50) => println!("Ottenuto 50"),
Some(n) if n == y => println!("Corrisposto, n = {n}"),
_ => println!("Caso predefinito, x = {:?}", x),
}
println!("alla fine: x = {:?}, y = {y}", x);
}
Questo codice stamperà ora Caso predefinito, x = Some(5). Il pattern nel secondo braccio match non introduce una nuova variabile y che farebbe ombra alla y esterna, il che significa che possiamo utilizzare la y esterna nella guardia match. Invece di specificare il pattern come Some(y), che avrebbe fatto ombra alla y esterna, specifichiamo Some(n). Questo crea una nuova variabile n che non fa ombra a nulla perché non c’è nessuna variabile n al di fuori del match.
La guardia match if n == y non è un pattern e quindi non introduce nuove variabili. Questa y è la y esterna anziché una nuova y ombreggiata e possiamo cercare un valore che abbia lo stesso valore della y esterna confrontando n con y.
È anche possibile utilizzare l’operatore or | in una guardia match per specificare più pattern; la condizione della guardia match si applicherà a tutti i pattern. Il Listato 18-28 mostra la precedenza quando si combinano un pattern che utilizza | con una guardia match. La parte importante di questo esempio è che la guardia match if y si applica a 4, 5 e 6, anche se potrebbe sembrare che if y si applichi solo a 6.
rust
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("sì"),
_ => println!("no"),
}
La condizione match stabilisce che il braccio corrisponde solo se il valore di x è uguale a 4, 5 o 6 e se y è true. Quando questo codice viene eseguito, il pattern del primo braccio corrisponde perché x è 4, ma la guardia match if y è false, quindi il primo braccio non viene scelto. Il codice passa al secondo braccio, che corrisponde, e questo programma stamperà no. Il motivo è che la condizione if si applica all’intero pattern 4 | 5 | 6, non solo all’ultimo valore 6. In altre parole, la precedenza di una guardia match rispetto a un pattern si comporta così:
(4 | 5 | 6) if y => …
piuttosto che questo:
4 | 5 | (6 if y) => …
Dopo aver eseguito il codice, il comportamento della precedenza è evidente: se la guardia match fosse applicata solo all’ultimo valore nella lista di valori specificati usando l’operatore |, il braccio sarebbe corrisposto e il programma avrebbe stampato sì. Bindings @
L’operatore @ ci consente di creare una variabile che contiene un valore contemporaneamente mentre stiamo testando quel valore per una corrispondenza del pattern. Nel Listato 18-29, vogliamo verificare che un campo id di Message::Hello sia compreso nell’intervallo 3..=7. Vogliamo anche legare il valore alla variabile id_variable in modo da poterlo utilizzare nel codice associato al braccio. Potremmo chiamare questa variabile id, come il campo, ma per questo esempio useremo un nome diverso.
rust
enum Message {
Hello { id: i32 },
} let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Trovato un id nell'intervallo: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Trovato un id in un altro intervallo")
}
Message::Hello { id } => println!("Trovato qualunque altro id: {}", id),
}
Questo codice stamperà Trovato un id nell’intervallo: 5. La differenza principale tra le variabili create con let e quelle create con @ è che le variabili create con @ si limitano al pattern specifico che segue l’@, in modo da non poter utilizzare @ per “rilevare” valori da più parti del pattern.