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 facilmente interpretare e a cui puoi 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 terminare il processo.
Ricorda da “Gestione del Potenziale Fallimento con Result
” nel Capitolo 2 che l'enum Result
è definito come avente due varianti, Ok
e Err
, come segue:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
I T
e E
sono parametri di tipi generici: discuteremo i generici più in dettaglio nel Capitolo 10. Quello che devi sapere ora è che T
rappresenta il tipo del valore che verrà restituito in caso di successo all'interno della variante Ok
, e E
rappresenta il tipo dell'errore che verrà restituito in 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 i valori di successo e di errore che vogliamo restituire possono differire.
Chiamiamo una funzione che restituisce un valore Result
perché la funzione potrebbe fallire. Nel Listing 9-3 proviamo ad aprire un file.
Nome File: src/main.rs
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 riempito dall'implementazione di File::open
con il tipo del valore di successo, std::fs::File
, che è un handle del file. Il tipo di E
utilizzato 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 alla funzione potrebbe anche fallire: ad esempio, il file potrebbe non esistere, o potremmo non avere i permessi per accedere al file. La funzione File::open
ha bisogno di un modo per dirci se ha avuto successo o fallito e, contemporaneamente, darci l'handle del file o le informazioni sull'errore. Questa informazione è 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 si è verificato.
Dobbiamo aggiungere al codice nel Listing 9-3 per intraprendere azioni diverse a seconda del valore restituito da File::open
. Il Listing 9-4 mostra un modo per gestire il Result
utilizzando uno strumento di base, l'espressione match
di cui abbiamo discusso nel Capitolo 6.
Nome File: src/main.rs
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!("Problem opening the file: {:?}", error), }; }
Nota che, come l'enum Option
, anche l'enum Result
e le sue varianti sono stati portati in ambito dal preambolo, quindi non è necessario specificare Result::
prima delle varianti Ok
e Err
nei bracci del match
.
Quando il risultato è Ok
, questo codice restituirà il valore interno file
dalla variante Ok
, e assegneremo dunque quell'handle del file alla variabile greeting_file
. Dopo il match
, possiamo usare l'handle del file per leggere o scrivere.
L'altro braccio del match
gestisce il caso in cui otteniamo un valore Err
da File::open
. In questo esempio, abbiamo scelto di chiamare la macro panic!
. Se non esiste un file chiamato hello.txt nella nostra attuale directory e eseguiamo questo codice, vedremo il seguente output dalla macro panic!
:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/error-handling`
thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Come al solito, questo output ci dice esattamente cosa è andato storto.
Gestione di Errori Diversi
Il codice nel Listing 9-4 eseguirà panic!
indipendentemente dal motivo per cui File::open
ha fallito. Tuttavia, vogliamo intraprendere azioni diverse per motivi di fallimento diversi. Se File::open
ha fallito perché il file non esiste, vogliamo creare il file e restituire l'handle al nuovo file. Se File::open
ha fallito per qualsiasi altra ragione—per esempio, perché non avevamo i permessi per aprire il file—vogliamo comunque che il codice esegua panic!
nello stesso modo in cui ha fatto nel Listing 9-4. Per questo, aggiungiamo un'espressione match
interna, mostrata nel Listing 9-5.
Nome File: src/main.rs
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
}
Il tipo del valore che File::open
restituisce all'interno della variante Err
è io::Error
, che è una struct fornita dalla libreria standard. Questa struct ha un metodo kind
che possiamo chiamare per ottenere un valore io::ErrorKind
. L'enum io::ErrorKind
è fornito dalla libreria standard e ha varianti che rappresentano i diversi tipi di errori che potrebbero derivare da un'operazione io
. La variante che vogliamo usare è ErrorKind::NotFound
, che indica che il file che stiamo tentando di aprire non esiste ancora. Quindi facciamo match
su greeting_file_result
, ma abbiamo anche un inner match su error.kind()
.
La condizione che vogliamo verificare nell'inner match è se il valore restituito da error.kind()
è la variante NotFound
dell'enum ErrorKind
. Se lo è, proviamo a creare il file con File::create
. Tuttavia, poiché anche File::create
potrebbe fallire, abbiamo bisogno di un secondo braccio nell'inner match expression. Quando il file non può essere creato, viene stampato un messaggio di errore diverso. Il secondo braccio dell'outer match rimane lo stesso, quindi il programma va in panico su qualsiasi errore diverso dall'errore di file mancante.
Alternative all'uso di
match
conResult<T, E>
Questo è un sacco di
match
! L'espressionematch
è molto utile ma anche molto primitiva. Nel Capitolo 13, imparerai riguardo ai closure, che vengono usati con molti dei metodi definiti suResult<T, E>
. Questi metodi possono essere più concisi rispetto all'uso dimatch
quando si gestiscono valoriResult<T, E>
nel tuo codice.Ad esempio, ecco un altro modo per scrivere la stessa logica mostrata nel Listing 9-5, questa volta usando closure e il metodo
unwrap_or_else
:use std::fs::File; use std::io::ErrorKind; fn main() { let greeting_file = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {error:?}"); }) } else { panic!("Problem opening the file: {error:?}"); } }); }
Anche se questo codice ha lo stesso comportamento del Listing 9-5, non contiene alcuna espressione
match
ed è più pulito da leggere. Torna a questo esempio dopo aver letto il Capitolo 13 e cerca il metodounwrap_or_else
nella documentazione della libreria standard. Molti altri di questi metodi possono ripulire enormi espressionimatch
annidate quando stai gestendo errori.
Scorciatoie per il Panico su Errore: unwrap
e expect
L'uso di match
funziona abbastanza bene, ma può essere un po' prolisso e non sempre comunica bene l'intento. Il tipo Result<T, E>
ha molti metodi helper definiti su di esso per fare varie operazioni più specifiche. Il metodo unwrap
è un metodo shortcut implementato proprio come l'espressione match
che abbiamo scritto nel Listing 9-4. Se il valore Result
è la variante Ok
, unwrap
restituirà il valore all'interno dell'Ok
. Se il Result
è la variante Err
, unwrap
chiamerà la macro panic!
per noi. Ecco un esempio di unwrap
in azione:
Nome File: src/main.rs
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt").unwrap(); }
Se eseguiamo questo codice senza un file hello.txt, vedremo un messaggio di errore dalla chiamata panic!
che il metodo unwrap
esegue:
thread 'main' panicked at src/main.rs:4:49:
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Allo stesso modo, il metodo expect
ci permette anche di scegliere il messaggio di errore panic!
. Utilizzare expect
invece di unwrap
e fornire buoni messaggi di errore può trasmettere il tuo intento e rendere più facile rintracciare la fonte di un panico. La sintassi di expect
è simile a questa:
Nome File: src/main.rs
use std::fs::File; fn main() { let greeting_file = File::open("hello.txt") .expect("hello.txt should be included in this project"); }
Usiamo expect
nello stesso modo di unwrap
: per restituire l'handle del file o chiamare la macro panic!
. Il messaggio di errore utilizzato da expect
nella sua chiamata a panic!
sarà il parametro che passiamo a expect
, piuttosto che il messaggio predefinito di panic!
che unwrap
utilizza. Ecco come appare:
thread 'main' panicked at src/main.rs:5:10:
hello.txt dovrebbe essere incluso in questo progetto: Os { code: 2, kind: NotFound, message: "No such file or directory" }
Nel codice di qualità per la produzione, la maggior parte dei Rustacean sceglie expect
piuttosto che unwrap
e fornisce più contesto sul perché l'operazione è attesa per avere sempre successo. In questo modo, se le tue assunzioni si rivelano mai sbagliate, avrai più informazioni disponibili per eseguire il debug.
Propagazione degli Errori
Quando l'implementazione di una funzione chiama qualcosa che potrebbe fallire, invece di gestire l'errore all'interno della funzione stessa puoi restituire l'errore al codice chiamante in modo che possa decidere cosa fare. Questo è conosciuto come propagazione dell'errore e dà più controllo al codice chiamante, dove potrebbe esserci più informazione o logica che detta come l'errore dovrebbe essere gestito rispetto a quello che hai disponibile nel contesto del tuo codice.
Ad esempio, il Listing 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.
Nome File: src/main.rs
#![allow(unused)] fn main() { 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 molte operazioni 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 ha successo senza nessun problema, il codice che chiama questa funzione riceverà un valore Ok
che contiene una String
—il username
che questa funzione ha letto dal file. Se questa funzione riscontra problemi, il codice chiamante riceverà un valore Err
che contiene un'istanza di io::Error
che contiene maggiori informazioni sui problemi riscontrati. Abbiamo scelto io::Error
come tipo di ritorno di questa funzione perché succede che sia 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 al match
nel Listing 9-4. Se File::open
ha successo, l'handle del file nella variabile 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 ritornare anticipatamente dalla funzione completamente e passare il valore di errore da File::open
, ora nella variabile 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 poi 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 nello stesso modo in cui abbiamo restituito il valore di errore nel match
che ha gestito il valore di ritorno di File::open
. Tuttavia, non abbiamo bisogno di dire esplicitamente return
, perché questa è l'ultima espressione nella funzione.
Il codice che chiama questo codice gestirà quindi l'ottenimento di un valore Ok
che contiene un nome utente o un valore Err
che contiene un io::Error
. Sta al codice chiamante decidere cosa fare con quei valori. Se il codice chiamante ottiene un valore Err
, potrebbe chiamare panic!
e far collassare il programma, usare un nome utente di default, 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 tentando di fare, quindi propaghiamo tutte le informazioni di successo o di errore verso l'alto affinché siano gestite adeguatamente.
Questo pattern di propagazione degli errori è così comune in Rust che Rust fornisce l'operatore punto interrogativo ?
per renderlo più facile.
Una Scorciatoia per Propagare gli Errori: l'Operatore ?
Il Listing 9-7 mostra un'implementazione di read_username_from_file
che ha la stessa funzionalità del Listing 9-6, ma questa implementazione utilizza l'operatore ?
.
Nome File: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username_file = File::open("hello.txt")?; let mut username = String::new(); username_file.read_to_string(&mut username)?; Ok(username) } }
?
posto dopo un valore Result
è definito per funzionare quasi nello stesso modo
delle espressioni match
che abbiamo definito per gestire i valori Result
nel Listato
9-6. Se il valore del Result
è un Ok
, il valore all'interno dell'Ok
verrà
restituito da questa espressione e il programma continuerà. Se il valore è un Err
, il Err
verrà restituito da tutta la funzione come se avessimo
usato la parola chiave return
in modo che il valore di errore venga propagato al codice chiamante.
C'è una differenza tra ciò che l'espressione match
dal Listato 9-6 fa
e ciò che fa l'operatore ?
: i valori di errore a cui viene chiamato l'operatore ?
passano attraverso la funzione from
, definita nel trait From
nella libreria standard, che viene usata per convertire i valori da un tipo a un altro.
Quando l'operatore ?
chiama la funzione from
, il tipo di errore ricevuto è
convertito nel tipo di errore definito nel tipo di ritorno della funzione corrente. Questo è utile quando una funzione ritorna un tipo di errore per rappresentare
tutti i modi in cui una funzione potrebbe fallire, anche se parti potrebbero fallire per molti motivi diversi.
Per esempio, potremmo cambiare la funzione read_username_from_file
nel Listato
9-7 per restituire un tipo di errore personalizzato chiamato OurError
che definiamo. Se definiamo anche impl From<io::Error> for OurError
per costruire un'istanza di
OurError
da un io::Error
, allora le chiamate dell'operatore ?
nel corpo di
read_username_from_file
chiameranno from
e convertiranno i tipi di errore senza
bisogno di aggiungere altro codice alla funzione.
Nel contesto del Listato 9-7, il ?
alla fine della chiamata File::open
restituirà
il valore all'interno di un Ok
alla variabile username_file
. Se si verifica un errore, l'operatore ?
restituirà anticipatamente l'intera funzione e fornirà
qualsiasi valore Err
al codice chiamante. La stessa cosa vale per il ?
alla fine della chiamata read_to_string
.
L'operatore ?
elimina molte boilerplate e rende l'implementazione di questa funzione più semplice. Potremmo anche abbreviarlo ulteriormente concatenando
le chiamate ai metodi immediatamente dopo il ?
, come mostrato nel Listato 9-8.
Nome del file: src/main.rs
#![allow(unused)] fn main() { use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let mut username = String::new(); File::open("hello.txt")?.read_to_string(&mut username)?; Ok(username) } }
Abbiamo spostato la creazione del nuovo String
in username
all'inizio della
funzione; quella parte non è cambiata. Invece di creare una variabile
username_file
, abbiamo concatenato la chiamata a read_to_string
direttamente
al risultato di File::open("hello.txt")?
. Abbiamo ancora un ?
alla fine della
chiamata read_to_string
, e rimanderemo ancora un valore Ok
contenente username
quando sia File::open
che read_to_string
riescono invece di restituire
errori. La funzionalità è di nuovo la stessa del Listato 9-6 e del Listato 9-7; questo è solo un modo diverso e più ergonomico di scriverlo.
Il Listato 9-9 mostra un modo per rendere questo ancora più breve utilizzando fs::read_to_string
.
Nome del file: src/main.rs
#![allow(unused)] fn main() { use std::fs; use std::io; fn read_username_from_file() -> Result<String, io::Error> { fs::read_to_string("hello.txt") } }
Leggere un file in una stringa è un'operazione piuttosto comune, quindi la libreria
standard fornisce la funzione comoda fs::read_to_string
che apre il file,
crea un nuovo String
, legge il contenuto del file, mette il contenuto
in quella String
, e la restituisce. Naturalmente, l'uso di fs::read_to_string
non ci dà la possibilità di spiegare tutta la gestione degli errori, quindi lo abbiamo fatto
nel modo più lungo prima.
Dove l'Operatore ?
Può Essere Utilizzato
L'operatore ?
può essere utilizzato solo in funzioni il cui tipo di ritorno è compatibile
con il valore su cui viene usato il ?
. Questo perché l'operatore ?
è definito
per eseguire un ritorno anticipato di un valore fuori dalla funzione, nello stesso modo
dell'espressione match
che abbiamo definito nel Listato 9-6. Nel Listato 9-6,
il match
stava usando un valore Result
, e il ramo di ritorno anticipato restituiva un
valore Err(e)
. Il tipo di ritorno della funzione deve essere un Result
così che
sia compatibile con questo return
.
Nel Listato 9-10, vediamo l'errore che otterremo se usiamo l'operatore ?
in una funzione main
con un tipo di ritorno che non è compatibile con il tipo del valore su cui usiamo ?
.
Nome del file: src/main.rs
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
Questo codice apre un file, che potrebbe fallire. L'operatore ?
segue il valore Result
ritornato da File::open
, ma questa funzione main
ha il tipo di ritorno
()
, non Result
. Quando compiliamo questo codice, otteniamo il seguente messaggio di errore:
$ cargo run
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:48
|
3 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
4 | let greeting_file = File::open("hello.txt")?;
| ^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling` due to previous error
Questo errore sottolinea che siamo autorizzati a usare l'operatore ?
solo in
una funzione che restituisce Result
, Option
, o un altro tipo che implementa
FromResidual
.
Per correggere l'errore, hai due scelte. Una scelta è cambiare il tipo di ritorno
della tua funzione per essere compatibile con il valore su cui stai usando l'operatore ?
finché non hai restrizioni che lo impediscono. L'altra scelta è utilizzare un match
o uno dei metodi Result<T, E>
per gestire il Result<T, E>
in qualunque modo sia appropriato.
Il messaggio di errore menzionava anche che ?
può essere utilizzato con i valori Option<T>
.
Come con l'uso di ?
su Result
, puoi usare ?
su Option
solo in una funzione che ritorna un Option
. Il comportamento dell'operatore ?
quando viene chiamato
su un Option<T>
è simile al suo comportamento quando viene chiamato su un Result<T, E>
:
se il valore è None
, il None
verrà restituito anticipatamente dalla funzione a
quel punto. Se il valore è Some
, il valore all'interno del Some
è il
valore risultante dell'espressione, e la funzione continua. Il Listato 9-11 ha
un esempio di una funzione che trova l'ultimo carattere della prima riga nel testo dato.
fn last_char_of_first_line(text: &str) -> Option<char> { text.lines().next()?.chars().last() } fn main() { assert_eq!( last_char_of_first_line("Hello, world\nHow are you today?"), Some('d') ); assert_eq!(last_char_of_first_line(""), None); assert_eq!(last_char_of_first_line("\nhi"), None); }
Questa funzione restituisce Option<char>
perché è possibile che ci sia un
carattere lì, ma è anche possibile che non ci sia. Questo codice prende la fetta di stringa text
come argomento e chiama il metodo lines
su di essa, che restituisce
un iteratore sulle righe nella stringa. Poiché questa funzione vuole esaminare la
prima riga, chiama next
sull'iteratore per ottenere il primo valore dell'iteratore.
Se text
è la stringa vuota, questa chiamata a next
restituirà None
, nel qual caso usiamo ?
per fermarci e restituire None
da
last_char_of_first_line
. Se text
non è la stringa vuota, next
restituirà un valore
Some
contenente una fetta di stringa della prima riga in text
.
Il ?
estrae la fetta di stringa e possiamo chiamare chars
su quella fetta di stringa
per ottenere un iteratore dei suoi caratteri. Siamo interessati all'ultimo carattere di questa prima riga, quindi chiamiamo last
per restituire l'ultimo elemento nell'iteratore.
Questo è un Opzione
perché è possibile che la prima riga sia la stringa vuota; ad esempio, se text
inizia con una riga vuota ma ha caratteri su
altre righe, come in "\nhi"
. Tuttavia, se c'è un ultimo carattere sulla prima
riga, verrà restituito nella variante Some
. L'operatore ?
nel mezzo ci dà un modo
conciso di esprimere questa logica, permettendoci di implementare la
funzione in una sola riga. Se non potessimo usare l'operatore ?
su Option
, dovremmo
implementare questa logica utilizzando più chiamate ai metodi o un'espressione match
.
Nota che puoi usare l'operatore ?
su un Result
in una funzione che restituisce
Result
, e puoi usare l'operatore ?
su un Option
in una funzione che restituisce Option
, ma non puoi mescolarli. L'operatore ?
non
converterà automaticamente un Result
in un Option
o viceversa; in quei casi,
puoi utilizzare metodi come il metodo ok
su Result
o il metodo ok_or
su Option
per fare la conversione esplicitamente.
Finora, tutte le funzioni main
che abbiamo usato restituiscono ()
. La funzione main
è speciale perché è il punto d'ingresso e il punto d'uscita di un programma eseguibile,
e ci sono restrizioni su quale possa essere il suo tipo di ritorno affinché il programma
funzioni come previsto.
Fortunatamente, main
può anche restituire un Result<(), E>
. Il Listato 9-12 ha il codice
del Listato 9-10, ma abbiamo cambiato il tipo di ritorno di main
in
Result<(), Box<dyn Error>>
e aggiunto un valore di ritorno Ok(())
alla fine. Questo
codice ora compilerà.
Nome del file: src/main.rs
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
Il tipo Box<dyn Error>
è un oggetto trait, di cui parleremo nella sezione
“Usare oggetti trait che consentono valori di tipi diversi” nel Capitolo 17. Per ora, puoi
leggere Box<dyn Error>
come “qualsiasi tipo di errore”. Usare ?
su un valore Result
in una funzione main
con il tipo di errore Box<dyn Error>
è consentito
perché permette qualsiasi valore Err
di essere restituito anticipatamente. Anche se il corpo di questa funzione main
restituirà solo errori del tipo std::io::Error
, specificando Box<dyn Error>
, questa firma rimarrà corretta anche se nella funzione main
verrà aggiunto altro codice che restituisce altri errori.
Quando una funzione main
restituisce un Result<(), E>
, l'eseguibile si chiuderà con
un valore di 0
se main
restituisce Ok(())
e si chiuderà con un valore diverso da zero se main
restituisce un valore Err
. Gli eseguibili scritti in C restituiranno interi
quando si chiudono: i programmi che si chiudono con successo restituiscono l'intero 0
, e i programmi che si chiudono con errore restituiscono un qualche intero diverso da 0
.
Anche Rust restituisce interi dagli eseguibili per essere compatibili con questa convenzione.
La funzione main
può restituire qualsiasi tipo che implementa il trait
std::process::Termination
, che contiene
una funzione report
che restituisce un ExitCode
. Consulta la documentazione della libreria standard per ulteriori informazioni sull'implementazione del trait Termination
per i tuoi tipi.
Ora che abbiamo discusso i dettagli della chiamata panic!
o del ritorno di Result
, ritorniamo all'argomento su come decidere quale sia appropriato usare in quali
casi.