Gestion des erreurs en Rust : de unwrap aux techniques avancées

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Gestion des erreurs en Rust : de unwrap aux techniques avancées



Contrairement aux langages avec exceptions (Java, Python, C++), Rust n'utilise pas le mécanisme try/catch. Au lieu de cela, le langage propose un système de types puissant et sûr pour la gestion des erreurs. Les développeurs débutants abusent souvent de unwrap(), ce qui provoque des paniques de l'application. Dans cet article, nous examinerons les techniques avancées de gestion des erreurs en Rust : de la création de types d'erreurs personnalisés à l'utilisation des crates thiserror et anyhow, et nous verrons comment émuler le comportement de try/catch.



1. Pourquoi Rust a-t-il abandonné try/catch ?



Dans les langages avec exceptions, le flux d'exécution peut être interrompu à tout moment. Cela complique l'analyse du code et peut entraîner des fuites de ressources. Rust propose deux approches principales :



  • Erreurs récupérables — le type Result<T, E> (succès Ok(T) ou erreur Err(E)).
  • Erreurs irrécupérables — la macro panic!, qui arrête le programme.


L'opérateur ? est l'équivalent Rust de try. Il extrait la valeur de Ok ou retourne immédiatement Err depuis la fonction.



use std::fs::File;use std::io::Read;

fn read_username(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; // si erreur — on la retourne let mut username = String::new(); file.read_to_string(&mut username)?; // encore "try" Ok(username.trim().to_string())}


2. Création de types d'erreurs personnalisés



Pour les projets complexes, un seul std::io::Error ne suffit pas. La meilleure pratique est de définir son propre type d'erreur à l'aide d'une énumération (enum). Cela permet de regrouper différents types d'erreurs et d'ajouter du contexte.



use std::fmt;

#[derive(Debug)]enum MyAppError { IoError(std::io::Error), ParseError(String), NetworkError { url: String, status: u16 },}

impl fmt::Display for MyAppError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { MyAppError::IoError(e) => write!(f, "Erreur IO : {}", e), MyAppError::ParseError(s) => write!(f, "Erreur d'analyse : {}", s), MyAppError::NetworkError { url, status } => { write!(f, "Erreur réseau pour {} : statut {}", url, status) } } }}

impl std::error::Error for MyAppError {} // on implémente le trait Error


Maintenant, on peut utiliser ? pour une conversion automatique, si on implémente le trait From :



impl From<std::io::Error> for MyAppError {    fn from(err: std::io::Error) -> MyAppError {        MyAppError::IoError(err)    }}

fn process_file(path: &str) -> Result<(), MyAppError> { let content = std::fs::read_to_string(path)?; // se convertit automatiquement if content.is_empty() { return Err(MyAppError::ParseError("fichier vide".into())); } Ok(())}


3. Crates avancés : thiserror et anyhow



Écrire manuellement toutes les implémentations de Display et From est fastidieux. Pour cela, il existe deux crates populaires :



thiserror — pour les bibliothèques

La crate thiserror génère l'implémentation de std::error::Error via une macro derive. Idéale pour les bibliothèques où une description précise des erreurs est nécessaire.



use thiserror::Error;

#[derive(Error, Debug)]pub enum DataError { #[error("Erreur IO : {0}")] Io(#[from] std::io::Error), #[error("UTF-8 invalide : {0}")] Utf8(#[from] std::str::Utf8Error), #[error("Échec personnalisé : {details}")] Custom { details: String },}

// On peut maintenant écrire :fn parse_data(bytes: &[u8]) -> Result<String, Da

Blogs

Book Recommendations