Abbiamo visto sinora quanto i tipi di dato rivestano un'importanza centrale in Rust e di come questo linguaggio ne offra di estremamente variegati e dettagliati. Eppure, ci sono casi che richiedono la definizione di una tipologia di dato che permetta solo la scelta tra una serie limitata di valori: si tratta delle enum
, strutture utilissime, presenti in molti linguaggi di programmazione.
Come definire un enum in Rust
Per definire una enum
, occorre:
- introdurre una definizione con la parola chiave
enum
; - scegliere il nome per il nuovo tipo di dato che verrà così generato;
- elencare tra parentesi graffe i valori che saranno contemplati.
Un esempio piuttosto scolastico ma che può costituire un buon approccio è quello del semaforo. Un semaforo stradale può avere solo tre stati rappresentati ognuno da un valore: rosso, verde o giallo. Se volessimo creare un tipo di dato per gestire un caso simile, una enum
sarebbe assolutamente ideale e potrebbe essere definita così:
enum Semaforo{
Rosso,
Verde,
Giallo,
}
Come si vede i valori interni vengono semplicemente elencati ma non viene assegnato niente a nessuno di essi. Per poter utilizzare queste grandezze sarà sufficiente invocarle anteponendo il nome della enum
, Semaforo
in questo caso, ed una coppia di due punti: Semaforo::Rosso
per il rosso, Semaforo::Giallo
per il giallo e Semaforo::Verde
per il verde.
Controllo del flusso ed enum
La principale attività svolta con le enum
è quella di definire un limitato numero di casi tra cui scegliere e gestire variabili dello stesso tipo della enum
per utilizzare costrutti per il controllo del flusso. Un caso particolarmente adatto alla gestione delle enum
è il costrutto match
già incontrato in precedenza. Eccone subito un esempio:
let colore:Semaforo=Semaforo::Verde;
match colore {
Semaforo::Rosso => println!("Fermati!"),
Semaforo::Verde => println!("Prego passa pure"),
Semaforo::Giallo => println!("Frena che tra poco scatta il rosso")
}
Come si vede, il match
contempla tre possibili casi, ognuno collegato con un valore dell'enum
: una struttura di questo genere permetterà di gestire ogni possibile situazione.
Option
Un caso di enum
che può risultare molto utile ed interessante soprattutto perché parte strumentale di Rust è Option
. Dà la possibilità di definire valori opzionali il che potrebbe sembrare un aspetto di nicchia ma in realtà risulta fondamentale quando un'operazione potrebbe non restituire un risultato in ogni caso. E' definita così:
enum Option {
None,
Some(T),
}
Vediamo che è essenzialmente composta da due valori: None
che indica un risultato nullo e Some
, l'eventualità che un risultato ci sia.
Per farne uso, impariamo subito due aspetti. Il primo è che Option
viene definito specificando il tipo di dato che dovrà gestire in caso di presenza di valore, ad esempio Option<u32>
prospetterà la conservazione di valori di tipo u32
. Secondo, esistono dei metodi molto importanti che sono is_some
per verificare se l'Option
contiene qualcosa, is_none
per verificare che sia vuoto e, nel caso ci sia un valore custodito, unwrap
per estrarlo. Usiamoli in sequenza con questo esempio:
fn main() {
let mut valore: Option<u32> = Some(2);
// se un valore esiste, lo stampiamo
if valore.is_some(){
println!("Il valore custodito è {}",valore.unwrap());}
// annulliamo il valore
valore=None;
if valore.is_none(){
println!("Non si può stampare perchè è vuoto!");}
}
Un caso molto utile è quando in Rust dobbiamo definire una funzione che in alcuni casi potrebbe non restituire un risultato. Pensiamo ad esempio a quelle operazioni matematiche impossibili come la divisione per zero. Potremmo creare una funzione in questo modo:
fn divisione(dividendo: f32, divisore: f32) -> Option<f32> {
if divisore!=0.0 {Some(dividendo / divisore)} else {None}
}
per poi richiamarla nel main
in questo modo:
fn main() {
let dividendo:f32 = 10.0;
let divisore:f32 = 3.0;
println!("Quanto fa {} diviso {}? ",dividendo, divisore);
let risultato:Option<f32> = divisione(dividendo, divisore);
if risultato.is_none() {
println!("Impossibile dividere per zero");
}
else {
println!("Risultato: {}",risultato.unwrap());
}
}
Il risultato stampato sarebbe:
Quanto fa 10 diviso 3?
Risultato: 3.3333333
Qualora cambiassimo il valore del divisore (non è mut
perché nelle nostre prove ricompiliamo l'esempio) impostandolo a 0.0 otterremmo questo output:
Quanto fa 10 diviso 0?
Impossibile dividere per zero