|
Importante per il linguaggio è il tipo Option, protagonista di questo paragrafo e di cui abbiamo già parlato. Si tratta sostanzialmente di un enumeratore che presenta al suo interno due varianti: - Some, che contiene un valore del tipo T specificato - None che è il caso vuoto, l'assenza di valore. internamente infatti Option è definito come segue: enum Option<T> { None, Some(T), } perchè è importante? un primo motivo è semplice, come abbiamo anche visto in precedenza, si tratta di un ottimo sistema per gestire situazioni che potrebbero mandare in panico, quindi in crash, il programma. Esso in un certo senso costringe il programmatore a prendere in considerazione quelle situazioni in cui potrebbe presentarsi un'assenza di valore, riducendo in modo significativo i rischi determinati da questa possibile situazione. La logica di funzionamento lo vediamo in un classico esempio che merita una piccola introduzione. Parlando delle operazioni algebriche abbiamo sottolineato il problema degli overflow, al quale abbiamo dedicato un paragrafo. E ricorderete che abbiamo parlato delle operazioni checked, le quali presentavano come output proprio un tipo Option. tipointero::checked_div(dividendo,divisore). internamente la funzione checked_div è definito come segue, nel caso degli i32: pub const fn checked_div(self, rhs: i32) -> Option<i32> la firma di questa funzione merita una descrizione, al fine che tutto sia chiaro: - pub è un modificatore che indica che la funzione è pubblicamente accessibile. In Rust i modificatori non sono così usati come in altri linguaggi ma ne parleremo. - const indica che la funzione può essere valutata a compile time (ricordate le costanti?) - fn come noto è la keyword che introduce le funzioni - checked_div è il nome della funzione - self e rhs sono i parametri, ovvero gli argomenti, i valori che passiamo alla funzione e su cui essa lavorerà, come vedremo nel paragrafo delle funzioni. In particolare self: Questo parametro indica che checked_div è un metodo definito su un'istanza di un tipo. Il tipo self suggerisce che checked_div è chiamato su un valore di un tipo numerico, come i32. L'uso di self senza una referenza (&self) o una mutabilità (&mut self) indica che il metodo consuma il valore su cui è chiamato, ma per i tipi primitivi come i32, che sono Copy, questo non è rilevante come per i tipi che non implementano Copy. Invece rhs: i32: questo è il parametro per il metodo. "rhs" sta per "right-hand side" (lato destro dell'operatore), il che è comune per operazioni binarie come l'addizione, la sottrazione o, in questo caso, la divisione. Il tipo i32 indica che il secondo operando è anch'esso un intero a 32 bit. - Option<I32> è invece l'elemento che ci interessa maggiormnte in questo capitolo. In pratica la funzione Checked_div ci restitutisce un tipo Option in cui il dato non nullo sarà un i32. possiamo vedere finalmente l'esempio:
Questo programma propone una divisione che, di norma, non si potrebbe fare, ovvero 7/0. Usando la divisione normale il programma andrebbe in crash, o meglio in gergo rusticeano, in panico. Tale situazione andrebbe gestita (con metodi che vedremo altrove) ma usando checked_div il risultato dell'esecuzione di questo programma sarà:
come proposto alla riga 7. Niente panico e il programma potrebbe continuare se vi fossero altre istruzioni. Il tipo option è estremamente utile, come è facile capire, in tanti casi simili, quindi per la gestione di situazioni anomale. Un problema che a volte si pone è quello estrarre materialmente il valore il valore che un Option si porta dietro all'interno di Some. Infatti se scrivessimo: let x = 7; let y = 3; let z = i32::checked_div(x, y); println!("{:?}", z); otterremo:
che non esattamente quello che potrebbe servirci nel senso che non è manipolabile come potrebbe esserlo un intero. Ci sono alcuni metodi abbastanza semplici per estrarre il valore: 1) usando unwrap, rischioso perchè il programma va in panico senza dirci nulla se il risultato è none. let x = 7; let y = 3; let z = i32::checked_div(x, y).unwrap(); se volete gestire il caso None senza panic potete usare unwrap_or(...) ad esempio modificherete l'ultima riga: let z = i32::checked_div(x,y).unwrap_or(numerochevolete) 2) utilizzando ovviamente match, gestendo il caso None: let x = 7; let y = 3; let risultato = match z { Some(numero) => numero, // Se z è Some, estrai il numero None => valorechevolete, // Se no valore da voi deciso }; println!("{}", risultato); // Stampa il valore estratto 3) con if-let per il quale presento un esempio completo:
Definire una variabile di tipo Option senza ricorrere a funzioni che lo facciano per noi, è molto semplice: let x = Some(5); Possiamo quindi usare questa variabile attraverso i metodi visti in precedenza
Prima di chiudere vediamo un altro esempio che non prende in esame i numeri ma le stringhe (è presente anche il concetto di lifetime che incontreremo nell'apposito paragrafo:
In linea generale possiamo dire, per riassumere, che i vantaggi di usare il tipo Option includono, in ordine sparso e senza pretesa di completezza:
Gestione degli errori: Option aiuta a gestire la
presenza o l’assenza di un valore in modo sicuro. Se un valore può
essere None (equivalente a null in altri linguaggi), Rust ti costringe a
gestire esplicitamente questa possibilità, riducendo gli errori a
runtime. |