Lezioni del modulo (2/2)
Gestione errori e operatore ?
La filosofia di Rust incoraggia a riconoscere la possibilità di errori in modo esplicito e a strutturare il codice per gestirli prima che il programma venga compilato.
Gli errori in Rust si dividono in due categorie principali: errori irrecuperabili (che causano il blocco immediato tramite il panico panic!) ed errori recuperabili (gestiti tramite il tipo Result<T, E>).
Il tipo Result<T, E> ed il Pattern Matching
La maggior parte degli errori recuperabili restituisce un tipo Result<T, E> che è un'enum definita come:
enum Result<T, E> {
Ok(T),
Err(E),
}
Possiamo usare il pattern matching per ispezionare il risultato:
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'aprire il file: {:?}", error),
};
}
Propagazione degli Errori con l'operatore ?
Quando implementiamo una funzione, invece di gestire l'errore direttamente al suo interno, spesso vogliamo restituire l'errore al chiamante affinché sia lui a decidere cosa fare. Questo processo è chiamato propagazione degli errori.
Rust fornisce l'operatore ? come scorciatoia sintattica per propagare gli errori. Se il valore di un Result è Ok, il valore interno a Ok viene restituito dall'espressione; se è Err, l'errore Err viene restituito dall'intera funzione corrente come se avessimo usato un return Err(...).
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("username.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
[!IMPORTANT] L'operatore
?può essere utilizzato solo in funzioni che restituiscono un tipo compatibile con il valore su cui viene applicato (generalmenteResult,Option, o tipi che implementanoFromResidual).
Errori Personalizzati
Possiamo definire i nostri tipi di errore personali implementando il trait std::fmt::Display (e opzionalmente std::error::Error) per fornire una rappresentazione leggibile dell'errore:
use std::fmt;
#[derive(Debug)]
struct MyError {
details: String,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Errore: {}", self.details)
}
}
impl std::error::Error for MyError {}
Prova tu
Esercizio 1: Leggere un file con l'operatore ?
Completa la funzione read_username_from_file in modo che utilizzi l'operatore ? per propagare gli errori generati dall'apertura del file e dalla lettura del suo contenuto nella stringa username.
Mostra suggerimento
Aggiungi il carattere `?`dopo`File::open(...)`e dopo`username_file.read_to_string(...)` per propagare gli errori.
Soluzione disponibile dopo 3 tentativi
Esercizio 2: Analizzare ed elaborare un numero
Scrivi una funzione chiamata parse_and_double che accetta in input un riferimento a stringa &str e restituisce un Result<i32, std::num::ParseIntError>. La funzione deve analizzare la stringa con val.parse::<i32>() usando ? per propagare l'errore di parsing, e in caso di successo restituire il valore raddoppiato racchiuso in Ok.
Mostra suggerimento
Usa `val.parse::<i32>()?`per convertire la stringa propagando l'errore, poi restituisci`Ok(num \* 2)`.
Soluzione disponibile dopo 3 tentativi
Esercizio 3: Definire un errore personalizzato
Definisci una struttura vuota chiamata CustomError. Implementa il trait std::fmt::Display per essa scrivendo all'interno del metodo fmt il messaggio 'Si e verificato un errore personalizzato'.
Mostra suggerimento
Dichiara `struct CustomError;`ed implementa Display scrivendo`write!(f, "Si e verificato un errore personalizzato")`nel metodo`fmt`.
Soluzione disponibile dopo 3 tentativi