Result in Rust
Se Option rappresenta un valore che potrebbe mancare, Result rappresenta un’operazione che potrebbe fallire. E a differenza di un valore mancante, un fallimento di solito porta con sé un motivo.
Com’è fatto Result
Sezione intitolata “Com’è fatto Result”Result è un’altra enum della libreria standard, questa volta con due varianti che contengono entrambe dei dati:
enum Result<T, E> { Ok(T), // successo, con un valore Err(E), // fallimento, con un errore}Una funzione che può fallire
Sezione intitolata “Una funzione che può fallire”.parse(), che hai incontrato nella pagina sul cast dei tipi, è un buon esempio reale: trasformare un testo in un numero può fallire se il testo non è davvero un numero:
fn main() { let good: Result<i32, std::num::ParseIntError> = "42".parse(); let bad: Result<i32, std::num::ParseIntError> = "abc".parse();
println!("{:?}", good); println!("{:?}", bad);}Output:
Ok(42)Err(ParseIntError { kind: InvalidDigit })InvalidDigit è il modo in cui Rust ti dice (in inglese) che nel testo c’è una cifra non valida.
Gestire un Result con match
Sezione intitolata “Gestire un Result con match”fn main() { let input = "42";
match input.parse::<i32>() { Ok(number) => println!("Convertito: {number}"), Err(e) => println!("Conversione fallita: {e}"), }}È la stessa forma che usi per gestire un Option con match, ma il ramo Err ti dà un vero valore di errore da esaminare o segnalare, non solo «non c’era niente».
Scrivere una funzione che restituisce Result
Sezione intitolata “Scrivere una funzione che restituisce Result”fn divide(a: f64, b: f64) -> Result<f64, String> { if b == 0.0 { Err(String::from("impossibile dividere per zero")) } else { Ok(a / b) }}
fn main() { match divide(10.0, 2.0) { Ok(result) => println!("Risultato: {result}"), Err(e) => println!("Errore: {e}"), }
match divide(10.0, 0.0) { Ok(result) => println!("Risultato: {result}"), Err(e) => println!("Errore: {e}"), }}Output:
Risultato: 5Errore: impossibile dividere per zeroRestituire Result<f64, String> obbliga chiunque chiami la funzione a fare i conti con un possibile fallimento: non c’è modo di trattare per sbaglio una divisione per zero come un numero normale.
L’operatore ?
Sezione intitolata “L’operatore ?”Concatenare diverse operazioni che possono fallire porta a blocchi match annidati su più livelli. L’operatore ? li appiattisce: vuol dire «se questo è Err, restituisci subito quell’errore dalla funzione corrente; altrimenti dammi il valore dentro Ok».
fn parse_and_double(input: &str) -> Result<i32, std::num::ParseIntError> { let number = input.parse::<i32>()?; // esce subito se la conversione fallisce Ok(number * 2)}
fn main() { println!("{:?}", parse_and_double("21")); println!("{:?}", parse_and_double("ops"));}Output:
Ok(42)Err(ParseIntError { kind: InvalidDigit })Senza ?, quella riga avrebbe bisogno del suo match solo per estrarre number o uscire in anticipo con l’errore: ? fa esattamente questo in un solo carattere. Funziona solo dentro una funzione che a sua volta restituisce un Result (o un Option), perché è lì che può restituire l’errore.
Result o panic!?
Sezione intitolata “Result o panic!?”Rust ha anche panic!, che ferma subito il programma: lo vedi più in dettaglio nella prossima pagina. Come regola pratica: usa Result per i fallimenti che chi chiama potrebbe ragionevolmente voler gestire (input sbagliato, un file mancante); tieni panic! per le situazioni che indicano un vero bug, in cui continuare sarebbe peggio che fermarsi.
Quando le tue funzioni possono fallire in tanti modi diversi, una semplice String come errore non basta più. La pagina sugli errori personalizzati mostra come definire un tuo tipo di errore, e quella su anyhow e thiserror mostra i crate che quasi tutti i progetti Rust usano per scrivere meno codice ripetitivo.
Pratica
Sezione intitolata “Pratica”1. Controlla un’età
Sezione intitolata “1. Controlla un’età”Scrivi fn parse_age(input: &str) -> Result<u32, String>. Deve restituire Err con un messaggio utile se il testo non è un numero, o se il numero supera 150. Provala con "34", "abc" e "200". Suggerimento: fai match su input.parse::<u32>() e, dentro il ramo Ok, usa un if per controllare il valore.
Mostra la soluzione
fn parse_age(input: &str) -> Result<u32, String> { match input.parse::<u32>() { Ok(age) => { if age > 150 { Err(format!("{age} anni sono troppi per essere veri")) } else { Ok(age) } } Err(_) => Err(format!("'{input}' non è un numero")), }}
fn main() { for input in ["34", "abc", "200"] { match parse_age(input) { Ok(age) => println!("Età: {age}"), Err(e) => println!("Errore: {e}"), } }}Output:
Età: 34Errore: 'abc' non è un numeroErrore: 200 anni sono troppi per essere veri2. Somma due numeri con ?
Sezione intitolata “2. Somma due numeri con ?”Scrivi fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError>, che converte entrambe le stringhe e restituisce la loro somma. Usa ? invece di match. Stampa il risultato per ("10", "20") e per ("10", "venti"). Suggerimento: ti serve un ? per ogni parse, e l’ultima riga avvolge la somma in Ok(...).
Mostra la soluzione
fn add_strings(a: &str, b: &str) -> Result<i32, std::num::ParseIntError> { let x = a.parse::<i32>()?; let y = b.parse::<i32>()?; Ok(x + y)}
fn main() { println!("{:?}", add_strings("10", "20")); println!("{:?}", add_strings("10", "venti"));}Output:
Ok(30)Err(ParseIntError { kind: InvalidDigit })Riepilogo
Sezione intitolata “Riepilogo”Result<T, E>rappresenta un’operazione che può riuscire (Ok) o fallire (Err) con un valore di errore.matchgestisce entrambi i casi in modo esplicito, con lo stesso schema diOption.- L’operatore
?restituisce subito unErrin automatico, così non ti serve unmatchdopo ogni chiamata che può fallire. - Preferisci
Resultapanic!per gli errori da cui chi chiama potrebbe ragionevolmente riprendersi.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Quali sono le due varianti di Result<T, E>?
2. Cosa fa l'operatore ? dentro una funzione che restituisce un Result?
3. Secondo questa pagina, quando conviene usare Result invece di panic!?
4. Nell'esempio divide di questa pagina, cosa restituisce divide(10.0, 0.0)?
5. Perché l'operatore ? funziona solo dentro una funzione che a sua volta restituisce un Result (o un Option)?
Punteggio: 0 / 5
Per approfondire
Sezione intitolata “Per approfondire”- Recoverable Errors with Result — The Rust Book (in inglese): il capitolo ufficiale su
Result,unwrap,expecte l’operatore?, con esempi di apertura di file. std::result— documentazione della libreria standard (in inglese): una panoramica su come lavorare conResult, compresi metodi comemap_err,unwrap_oreok.- Result — Rust by Example (in inglese): esempi eseguibili di gestione di
Resulte di come restituirlo damain. - The ? operator — Rust by Example (in inglese): uno sguardo breve e mirato a come
?sostituisce i blocchimatchannidati.