Home Rhyylen
Contatto
 
 
 
Rust Language
Capitolo 33
Gestione errori - Result

Abbiamo detto che gli enumerativi sono molto utilizzati all'interno del linguaggio e, tra le altre cose, il tipo Option è un esempio, estremamente importante di questi elementi. In questo paragrafo incontriamo Result che, enumerativo anche lui, serve per la gestione di situazioni di errore in genere più complesse rispetto ad Option. Ripetiamo subito che quest'ultimo, come abbiamo visto, restituisce Some se abbiamo un risultato significativo oppure None, se non otteniamo nulla. Sarà poi compito del programmatore intercettare questo None ed esporre eventualmente il messaggio corretto. Ora anche con Result abbiamo una situazione molto simile infatti la definizione di quest'ultimo internamente al linguaggio è la seguente:

enum Result<T, E> {
Ok(T),
Err(E),
}


che risulta del tutto simile alla definizione di Option, che ripeto qui di seguito:

enum Option<T> {
None,
Some(T),
}

Nella definizione di Result oltre al risultato che otteniamo in caso di successo, è previsto anche un elemento Err che conterrà l'errore. Da un punto di vista logico Option e Result rispondono in modo simile a domande diverse: Option gestisce situazioni nella quali può esservi un risultato oppure nulla. Result invece riferisce ai casi può esserci successo o errore ed è specifico per queste situazioni, la definizione formale stessa indica che, al contrario di None, possiamo fornire un parametro in più in quanto si suppone che un errore, diversamente da una semplice mancanza di dati, esponga una spiegazione. Vediamo un esempio:

  Esempio 33.1
1
2
3
4
5
6
7
8
9
10
11
12
13
fn divide_result(a: i32, b: i32) -> Result<i32, &'static str> {
    if b == 0 {
        Err("Divisione per zero!")
    } else {
        Ok(a / b)
    }
}
fn main() {
    match divide_result(10, 0) {
        Ok(result) => println!("Risultato: {}", result),
        Err(e) => println!("Error: {}", e),
    }
}

potremmo ottenere un risultato analogo con Option ma attenzione alla differenza:

  Esempio 33.2
1
2
3
4
5
6
7
8
9
10
11
12
13
fn divide_option(a: i32, b: i32) -> Option<i32> {
    if b == 0 {
        None
    } else {
        Some(a / b)
    }
}
fn main() {
    match divide_option(10, 0) {
        Some(result) => println!("Risultato: {}", result),
        None => println!("Dvisione per zero!"),
    }
}

L'output sarà uguale ma, come si può notare, nel primo caso abbiamo un gestione diretta dell'errore da parte di Result, nel secondo caso si tratta dell'analisi del caso in cui riceviamo un None. Per semplici esempi come quello proposto non cambia gran che ma in casi più complessi, con moduli cooperanti in progetti più ampi, si capisce subito come ricevere un segnale di errore specifico per la situazione che si verifica sia molto più semplice dell'analisi di una generica mancanza di risultato. In assoluto si potrebbe anche dire che:
Option - è più adatto quando la mancanza di un valore non è considerata un errore, ma una condizione normale (ad esempio, cercare un elemento in una mappa che potrebbe non essere presente).
Result - è più appropriato per operazioni che possono fallire e dove è importante sapere perché l'operazione è fallita (ad esempio, operazioni I/O, calcoli che possono fallire come la divisione per zero).
Un interessante esempio arriva dal testo di David Mcleod citato nel paragrafo delle fonti ed è il seguente:

fn check_error() -> Result<(), ()> {
    Ok(())    
}
fn main() {
    check_error();
}

questo codice compila ma il compilatore ci dà un warning molto esplicito che inizia così:

warning: unused `Result` that must be used

con un invito esplicito a fare un uso pieno di Result.
Sui forum si discute a volte sull'uso di Result e Option. Senza andare troppo per il sottile possiamo dare questo enunciato di validità molto generico che ribadisce quanto detto:
  • Usa Option quando l'assenza di un valore è una possibilità normale e non c'è bisogno di ulteriori informazioni sull'errore. Ovvero quando è possibile la presenza o l'assenza di un valore. Per esempio una classica operazione di ricerca di un valore in una lista che restituisce None se l'elemento non c'è è perfettamente adatta per essere gestita tramite Option.
  • Usa Result quando è importante fornire dettagli sull'errore e si desidera gestire fallimenti specifici in modo appropriato. Ovvero quando è possibile che l'operazione fallisca con un errore specifico. Ad esempio operazioni di I/O su file o periferiche oppure complesse attività di parsing si gioveranno sensibilmente dell'uso di Result.
Prima di chiudere vediamo due metodi estremamente utilizzati in congiunzione con Result e che abbiamo già incontrato in qualhe esempio. Si tratta di:

Result::expect()
Reuslt::unwrap()


La differenza tra i due è abbastanza sottile in apparenza ma in realtà può essere cruciale nei vostri progetti. Consultate le varie spiegazioni disponibili, si può giungere a questa conclusione:

** Entrambi i metodi servono per estrarre il valore eventualmente ottenuto
**
unwrap() tenta di estrarre il valore Ok(T) da un Result. Se il risultato è un errore (Err(E)), il programma termina immediatamente con un panic, mostrando un messaggio generico (es. "called Result::unwrap()on anErr value").
Uso consigliato: in contesti in cui sei assolutamente sicuro che il Result sia Ok, come durante lo sviluppo rapido o per prototipi.
**
expect() è simile a unwrap(), ma permette di specificare un messaggio personalizzato che verrà mostrato se il programma va in panic (invece del messaggio generico di unwrap).
Uso consigliato: per debugging e produzione: permette di sapere dove e perché si è verificato l'errore grazie alla sua maggior capacità descrittiva.
Vediamo un piccolo esempio:

  Esempio 33.3
1
2
3
4
5
6
7
fn main() {
    let result: Result<i32, &str> = Ok(10);
    let value = result.unwrap(); // Restituisce 10
    println!("{}", std::any::type_name_of_val(&value));
    let result2: Result<i32, &str> = Err("errore");
    let value = result2.expect("Il risultato non Ok"); //panico
}

 
In realtà Result è ancora concettualmente qualche cosa di più profondo dal punto di vista di progetti di ampio respiro ma in questa fase possiamo accontentarci di quanto visto finora.