Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories
 Corso Gratuito di Programmazione Rust Lezione 032 – Errori recuperabili con risultato

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Errori recuperabili con Result

La maggior parte degli errori non sono così gravi da richiedere l’arresto completo del programma. A volte, quando una funzione fallisce, è per una ragione che puoi interpretare facilmente e rispondere. Ad esempio, se provi ad aprire un file e quell’operazione fallisce perché il file non esiste, potresti voler creare il file invece di interrompere il processo.

Ricorda da “Gestione dei potenziali errori con Result” nel Capitolo 2 che l’enum Result è definito avendo due varianti, Ok ed Err, come segue:

rust

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

Il T e l’E sono parametri di tipo generico: discuteremo dei generici in modo più dettagliato nel Capitolo 10. Quello che devi sapere adesso è che T rappresenta il tipo del valore che verrà restituito in un caso di successo all’interno della variante Ok, e E rappresenta il tipo dell’errore che verrà restituito in un caso di fallimento all’interno della variante Err. Poiché Result ha questi parametri di tipo generico, possiamo usare il tipo Result e le funzioni definite su di esso in molte situazioni diverse in cui il valore di successo e il valore di errore che vogliamo restituire possono differire.

Chiamiamo una funzione che restituisce un valore Result perché la funzione potrebbe fallire. Nel Listato 9-3 proviamo ad aprire un file.

rust

use std::fs::File;

fn main() {
let greeting_file_result = File::open("hello.txt");
}

Il tipo di ritorno di File::open è un Result<T, E>. Il parametro generico T è stato compilato dall’implementazione di File::open con il tipo del valore di successo, std::fs::File, che è un handle del file. Il tipo di E usato nel valore di errore è std::io::Error. Questo tipo di ritorno significa che la chiamata a File::open potrebbe avere successo e restituire un handle del file da cui possiamo leggere o scrivere. La chiamata di funzione potrebbe anche fallire: ad esempio, il file potrebbe non esistere, o potremmo non avere il permesso di accedere al file. La funzione File::open deve avere un modo per dirci se ha avuto successo o meno e allo stesso tempo darci o l’handle del file o le informazioni sull’errore. Queste informazioni sono esattamente ciò che l’enum Result trasmette.

Nel caso in cui File::open abbia successo, il valore nella variabile greeting_file_result sarà un’istanza di Ok che contiene un handle del file. Nel caso in cui fallisca, il valore in greeting_file_result sarà un’istanza di Err che contiene più informazioni sul tipo di errore che è accaduto.

Dobbiamo aggiungere al codice nel Listato 9-3 per prendere azioni diverse a seconda del valore che File::open restituisce. Nel Listato 9-4 viene mostrato un modo per gestire il Result utilizzando uno strumento di base, l’espressione match che abbiamo discusso nel Capitolo 6.

rust

use std::fs::File; fn main() {
let greeting_file_result = File::open("hello.txt");

let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problema nell'apertura del file: {:?}", error),
};
}

Si noti che, come l’enum Option, l’enum Result e le sue varianti sono state importate nello scope dal preludio, quindi non è necessario specificare Result:: prima delle varianti Ok ed Err nei rami del match.

Quando il risultato è Ok, questo codice restituirà il valore del file interno dalla variante Ok, e quindi assegniamo quel valore di handle del file alla variabile greeting_file. Dopo il match, possiamo usare l’handle del file per la lettura o la scrittura.

L’altro braccio del match gestisce il caso in cui otteniamo un valore Err da File::open. In questo esempio, abbiamo scelto di chiamare il macro panic!. Se non c’è un file chiamato hello.txt nella nostra directory corrente e eseguiamo questo codice, vedremo l’output seguente dal macro panic!:

css

$ cargo run
Compilazione del progetto error-handling v0.1.0 (file:///projects/error-handling)
Terminato dev [non ottimizzato + debuginfo] target(s) in 0.73s
Esecuzione di `target/debug/error-handling`
Il thread 'main' ha panic! a 'Problema nell'apertura del file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
Nota: eseguire con la variabile d'ambiente `RUST_BACKTRACE=1` per visualizzare una traccia completa

Come al solito, questo output ci dice esattamente cosa è andato storto.

Propagazione degli Errori

Quando l’implementazione di una funzione chiama qualcosa che potrebbe fallire, anziché gestire l’errore all’interno della funzione stessa, puoi restituire l’errore al codice chiamante in modo che possa decidere cosa fare. Questo è noto come propagazione dell’errore e dà più controllo al codice chiamante, dove potrebbero esserci più informazioni o logica che dettano come l’errore dovrebbe essere gestito rispetto a quanto hai disponibile nel contesto del tuo codice.

Per esempio, il Listato 9-6 mostra una funzione che legge un nome utente da un file. Se il file non esiste o non può essere letto, questa funzione restituirà quegli errori al codice che ha chiamato la funzione.

rust

use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt"); let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
}; let mut username = String::new();

match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}

Questa funzione può essere scritta in modo molto più breve, ma inizieremo facendo molto del lavoro manualmente per esplorare la gestione degli errori; alla fine, mostreremo il modo più breve. Guardiamo prima il tipo di ritorno della funzione: Result<String, io::Error>. Questo significa che la funzione restituisce un valore del tipo Result<T, E> dove il parametro generico T è stato riempito con il tipo concreto String, e il tipo generico E è stato riempito con il tipo concreto io::Error.

Se questa funzione riesce senza problemi, il codice che chiama questa funzione riceverà un valore Ok che contiene una String – il nome utente che questa funzione ha letto dal file. Se questa funzione incontra problemi, il codice chiamante riceverà un valore Err che contiene un’istanza di io::Error che contiene più informazioni su quali sono stati i problemi. Abbiamo scelto io::Error come tipo di ritorno di questa funzione perché quello accade ad essere il tipo del valore di errore restituito da entrambe le operazioni che stiamo chiamando nel corpo di questa funzione che potrebbero fallire: la funzione File::open e il metodo read_to_string.

Il corpo della funzione inizia chiamando la funzione File::open. Poi gestiamo il valore Result con un match simile a quello nel Listato 9-4. Se File::open ha successo, l’handle del file nella variabile di pattern file diventa il valore nella variabile mutabile username_file e la funzione continua. Nel caso di Err, invece di chiamare panic!, usiamo la parola chiave return per tornare subito fuori dalla funzione interamente e passare il valore di errore da File::open, ora nella variabile di pattern e, al codice chiamante come valore di errore di questa funzione.

Quindi, se abbiamo un handle del file in username_file, la funzione crea una nuova String nella variabile username e chiama il metodo read_to_string sull’handle del file in username_file per leggere il contenuto del file in username. Il metodo read_to_string restituisce anche un Result perché potrebbe fallire, anche se File::open ha avuto successo. Quindi abbiamo bisogno di un altro match per gestire quel Result: se read_to_string ha successo, allora la nostra funzione ha avuto successo, e restituiamo il nome utente dal file che ora è in username avvolto in un Ok. Se read_to_string fallisce, restituiamo il valore di errore allo stesso modo in cui abbiamo restituito il valore di errore nel match che ha gestito il valore di ritorno di File::open. Tuttavia, non dobbiamo dire esplicitamente return, perché questa è l’ultima espressione nella funzione.

Il codice che chiama questo codice quindi gestirà l’ottenere un valore sia Ok che contiene un nome utente, sia un valore Err che contiene un io::Error. Sta al codice chiamante decidere cosa fare con quei valori. Se il codice chiamante riceve un valore Err, potrebbe chiamare panic! e far crashare il programma, utilizzare un nome utente predefinito o cercare il nome utente da qualche altra parte che non sia un file, ad esempio. Non abbiamo abbastanza informazioni su ciò che il codice chiamante sta effettivamente cercando di fare, quindi propaghiamo tutte le informazioni di successo o di errore verso l’alto affinché possa gestirle in modo appropriato.

Questo modello di propagazione degli errori è così comune in Rust che Rust fornisce l’operatore di domanda ? per rendere questo più facile.

1 Comment

Leave a Reply

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *