ProgrammazioneProgrammatore di sistema

Spiega come avviene la gestione degli errori tramite Result<T, E> e come utilizzare correttamente l'operatore di punto interrogativo (?), senza compromettere la sicurezza e la leggibilità del codice.

Supera i colloqui con l'assistente IA Hintsage

Risposta.

Rust si basa su una gestione esplicita degli errori: le eccezioni sono assenti, invece viene utilizzato un valore di ritorno Result<T, E>. Questo garantisce la sicurezza e la prevedibilità del codice.

Storia della questione:

Molti linguaggi hanno seguito la strada delle eccezioni, il che ha portato a situazioni inaspettate e alla necessità di gestire esplicitamente le eccezioni a runtime. Rust fin dall'inizio ha puntato sul controllo tramite tipi, tutti gli errori devono essere parte della firma della funzione.

Problema:

La sfida principale è scrivere codice in cui gli errori non vengano ignorati o mascherati, senza funzioni che "cadono", ma mantenendo il codice compatto e leggibile. È necessario propagare correttamente gli errori a un livello superiore, senza perdere informazioni sul loro tipo e senza confondere la logica.

Soluzione:

Lo strumento chiave è il tipo Result<T, E> e l'operatore ?, che "espande" automaticamente il risultato: in caso di errore, avviene un'uscita immediata dalla funzione con il ritorno dell'errore, e in caso di successo viene restituito il valore.

Esempio di codice:

fn read_number(file: &str) -> Result<i32, std::io::Error> { let content = std::fs::read_to_string(file)?; let num: i32 = content.trim().parse()?; Ok(num) }

Caratteristiche chiave:

  • Dichiarazione esplicita di tutti i potenziali errori nella firma
  • L'operatore ? consente di semplificare la nidificazione (rimuove if-let/unwrap/expect)
  • Gli errori possono essere facilmente "incapsulati" o trasformati (attraverso .map_err() e altri metodi)

Domande insidiose.

È possibile utilizzare ? nelle funzioni che restituiscono Unit (void)?

No, l'operatore ? è consentito solo all'interno di funzioni che restituiscono Result o Option. Se la funzione restituisce (), non è possibile usare ?.

Cosa succede se i tipi di errore in più chiamate con ? sono diversi?

Si ottiene un errore di compilazione: il tipo di errore deve essere chiaramente definito. È necessario unificare tutti gli errori in un tipo unico tramite .map_err() oppure usare thiserror, o descrivere un enum-wrapper a livello API. Esempio:

fn foo() -> Result<_, MyError> { let a = bar()?; let b = baz().map_err(MyError::Baz)?; Ok() }

Qual è il problema di .unwrap() nella logica interna?

Esiste una comune idea sbagliata che nel codice "principale" sia possibile utilizzare liberamente unwrap(), "non fallirà di certo". In realtà, anche un piccolo errore invisibile porterà a un panico a runtime — compromettendo la sicurezza del programma.

Errori tipici e anti-pattern

  • Catturare tutti gli errori tramite unwrap/expect, soprattutto nella logica interna
  • Perdita di contesto con map_err(|_| …), quando l'errore viene chiuso senza mantenere le informazioni originali
  • Nidificazione eccessiva durante la gestione manuale di ogni errore senza ?

Esempio dalla vita reale

Caso negativo

Nel codice di produzione sono rimasti molti unwrap() dopo una rapida fase di debugging. Di conseguenza, l'applicazione è crollata in caso di errori di parsing dovuti a input non validi dell'utente.

Pro:

  • Debugging rapido

Contro:

  • Potenziale crash dell'applicazione, difficile individuare la causa

Caso positivo

È stato utilizzato un intero stack Result<T, E> con una descrizione esplicita degli errori e sono stati impiegati solo ? e .map_err(), mantenendo tutte le informazioni sul guasto.

Pro:

  • Facilità di debugging, comportamento prevedibile

Contro:

  • Un po' più di "rumore" nei tipi di errore