Spesso, un semplice if/else non basta, perché ci sono più di due
opzioni possibili. Inoltre, le condizioni possono diventare parecchio
complesse. Rust ha una parola-chiave, match ("combacia"), che consente
di sostituire dei complicati raggruppamenti di if/else
con qualcosa di più potente. Ecco qua:
let x = 5; match x { 1 => println!("uno"), 2 => println!("due"), 3 => println!("tre"), 4 => println!("quattro"), 5 => println!("cinque"), _ => println!("qualcos'altro"), }
match prende un'espressione ed esegue una diramazione in base al suo valore.
Ogni ‘braccio’ della diramazione ha la forma valore => espressione.
Quando il valore combacia, l'espressione di quel braccio viene valutata.
Viene chiamata match a causa del concetto di ‘pattern matching’, di cui
match è un'implementazione. C'è un separate section on patterns
che tratta di tutti i pattern che sono ammessi qui.
Uno dei molti vantaggi di match è che impone la ‘verifica di esaustività’.
Per esempio, se si toglie l'ultimo braccio, quello con il carattere _,
il compilatore darà l'errore:
error: non-exhaustive patterns: `_` not covered
Rust ci dice che abbiamo dimenticato qualche valore. Il compilatore inferisce
dall'x che può avere qualunque valore a 32 bit, da -2.147.483.648
a 2.147.483.647. Il carattere _ agisce da 'prendi-tutto', e prenderà
tutti i possibili valori che non sono specificati in un braccio
dello stesso match. Come si vede nell'esempio precedente, al match
vengono forniti bracci per gli interi da 1 a 5, se x vale 6 o qualunque
altro valore, viene preso dal caso _.
Il costrutto match è anche un'espressione, il che significa che lo si può
usare al lato destro di un'istruzione let o direttamente dove è ammessa
una espressione:
let x = 5; let numero = match x { 1 => "uno", 2 => "due", 3 => "tre", 4 => "quattro", 5 => "cinque", _ => "qualcos'altro", };
Talvolta è un modo carino di convertire qualcosa da un tipo a un altro; in
questo esempio gli interi vengono convertiti in String.
Un altro impiego importante della parola-chiave match sta nell'elaborare
le possibili varianti di un'enumerazione:
enum Messaggio { Abbandona, CambiaColore(i32, i32, i32), Sposta { x: i32, y: i32 }, Scrivi(String), } fn abbandona() { /* ... */ } fn cambia_colore(r: i32, g: i32, b: i32) { /* ... */ } fn sposta_cursore(x: i32, y: i32) { /* ... */ } fn elabora_messaggio(msg: Messaggio) { match msg { Messaggio::Abbandona => abbandona(), Messaggio::CambiaColore(r, g, b) => cambia_colorw(r, g, b), Messaggio::Sposta { x: x, y: y } => sposta_cursore(x, y), Messaggio::Scrivi(s) => println!("{}", s), }; }
Ancora, il compilatore Rust verifica l'esaustività, e richiede di avere
un braccio combaciante per ogni variante dell'enum. Se ne manca qualcuno, darà
un errore di compilazione, a meno che si usi il braccio _.
Diversamente dai precedenti utilizzi di match, questo caso non è
sostituibile da un semplice uso del costrutto if. Si può però usare
il costrutto if let, che può essere visto come una forma
abbreviata di match.