Errori Personalizzati in Rust
Rust incoraggia la creazione di tipi di errore personalizzati per gestire le condizioni di errore in modo preciso e type-safe.
Il Trait std::error::Error
Per creare un errore personalizzato, implementa Display, Debug e Error:
use std::fmt;
#[derive(Debug)]
enum ErroreApp {
NonTrovato(String),
NonAutorizzato,
Database(String),
}
impl fmt::Display for ErroreApp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErroreApp::NonTrovato(risorsa) =>
write!(f, "Risorsa non trovata: {}", risorsa),
ErroreApp::NonAutorizzato =>
write!(f, "Accesso non autorizzato"),
ErroreApp::Database(dettaglio) =>
write!(f, "Errore database: {}", dettaglio),
}
}
}
impl std::error::Error for ErroreApp {}
Conversione con From
Implementa From per convertire errori di librerie esterne nel tuo tipo:
impl From<std::io::Error> for ErroreApp {
fn from(e: std::io::Error) -> Self {
ErroreApp::Database(e.to_string())
}
}
impl From<std::num::ParseIntError> for ErroreApp {
fn from(e: std::num::ParseIntError) -> Self {
ErroreApp::Database(e.to_string())
}
}
// Ora puoi usare ? per convertire automaticamente
fn leggi_configurazione() -> Result<i32, ErroreApp> {
let contenuto = std::fs::read_to_string("config.txt")?; // io::Error → ErroreApp
let valore: i32 = contenuto.trim().parse()?; // ParseIntError → ErroreApp
Ok(valore)
}
Il Crate thiserror
thiserror semplifica la creazione di errori con derive macro:
use thiserror::Error;
#[derive(Error, Debug)]
enum ErroreApp {
#[error("Risorsa non trovata: {0}")]
NonTrovato(String),
#[error("Accesso non autorizzato")]
NonAutorizzato,
#[error("Errore I/O")]
Io(#[from] std::io::Error),
#[error("Errore di parsing")]
Parse(#[from] std::num::ParseIntError),
#[error("Errore database: {messaggio}")]
Database {
messaggio: String,
#[source]
causa: Box<dyn std::error::Error>,
},
}
#[from] genera automaticamente l’implementazione di From.
Il Crate anyhow
anyhow è ideale per applicazioni (non librerie) dove non serve un tipo errore specifico:
use anyhow::{Result, Context, bail, anyhow};
fn leggi_config(percorso: &str) -> Result<String> {
let contenuto = std::fs::read_to_string(percorso)
.context(format!("Impossibile leggere {}", percorso))?;
if contenuto.is_empty() {
bail!("Il file di configurazione è vuoto");
}
Ok(contenuto)
}
fn main() -> Result<()> {
let config = leggi_config("app.toml")?;
println!("{}", config);
Ok(())
}
Gerarchia di Errori
use thiserror::Error;
#[derive(Error, Debug)]
enum ErroreRete {
#[error("Timeout dopo {0} secondi")]
Timeout(u64),
#[error("Connessione rifiutata")]
ConnessioneRifiutata,
}
#[derive(Error, Debug)]
enum ErroreServizio {
#[error("Errore di rete")]
Rete(#[from] ErroreRete),
#[error("Risposta non valida: {0}")]
RispostaNonValida(String),
}
Best Practice
- Librerie: usa
thiserrorcon tipi errore specifici - Applicazioni: usa
anyhowper semplicitĂ - Implementa
Fromper conversioni automatiche con? - Usa
#[source]per collegare la causa originale dell’errore - Non esporre dettagli interni negli errori rivolti all’utente
Conclusione
Rust offre un sistema di gestione errori potente e flessibile. Per librerie, crea errori personalizzati con thiserror per dare ai consumatori il massimo controllo. Per applicazioni, anyhow semplifica la propagazione e il contesto degli errori. In entrambi i casi, l’operatore ? con i trait From e Error rende la gestione degli errori ergonomica e sicura.