00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

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 thiserror con tipi errore specifici
  • Applicazioni: usa anyhow per semplicitĂ 
  • Implementa From per 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.