Gestion des erreurs en Rust : try/catch et bien plus — niveau avancé
\n\nContrairement à de nombreux langages où les exceptions sont un mécanisme standard (comme try/catch en Python, Java ou C++), Rust propose une approche fondamentalement différente. Il n'y a pas d'exceptions au sens classique du terme. À la place, le langage utilise un puissant système de types pour représenter et gérer les erreurs. Pour un débutant, cela peut sembler complexe, mais à un niveau avancé, vous apprécierez la sécurité et la prévisibilité qu'offre cette approche. Dans cet article, nous allons voir comment travailler professionnellement avec les erreurs en Rust : des bases Result et Option jusqu'aux crates avancés anyhow et thiserror, en passant par des parallèles avec le try/catch familier.
1. Fondamentaux : Result, Option et l'opérateur ?
\n\nLa base de la gestion des erreurs en Rust est l'énumération Result<T, E>. Elle peut être soit Ok(T) (succès), soit Err(E) (erreur). L'équivalent de try/catch peut être considéré comme l'opérateur ?. Il simplifie la remontée d'erreur : si la valeur est Err, la fonction la retourne immédiatement ; si elle est Ok, elle extrait la valeur.
use std::fs::File;\nuse std::io::Read;\n\nfn read_file(path: &str) -> Result<String, std::io::Error> {\n let mut file = File::open(path)?; // Équivalent de try : remontée d'erreur\n let mut content = String::new();\n file.read_to_string(&mut content)?;\n Ok(content)\n}\n\nDans cet exemple, File::open et read_to_string peuvent retourner Err. L'opérateur ? remplace un match verbeux et rend le code aussi propre qu'un try/catch dans d'autres langages. Cependant, contrairement aux exceptions, vous spécifiez explicitement le type d'erreur dans la signature de la fonction — ce qui améliore la lisibilité et la sécurité.
L'énumération Option<T> est utilisée pour les cas où une valeur peut être absente (équivalent de None en Python). L'opérateur ? fonctionne également avec elle :
fn get_first_element(vec: &Vec<i32>) -> Option<&i32> {\n vec.first()? // Si le vecteur est vide, on retourne None\n}\n\n2. Travail avancé avec les types d'erreur : les traits From et Error
\n\nLorsqu'une fonction appelle plusieurs opérations avec différents types d'erreur (par exemple, entrées-sorties et analyse syntaxique), un problème se pose : comment retourner un type unique ? La solution réside dans le trait From et un type d'erreur personnalisé.
Créons notre propre structure d'erreur et implémentons pour elle le trait std::error::Error (automatiquement via #[derive(Debug)] et une implémentation manuelle de Display) :
use std::fmt;\nuse std::io;\n\n#[derive(Debug)]\nenum MyError {\n Io(io::Error),\n Parse(std::num::ParseIntError),\n}\n\nimpl fmt::Display for MyError {\n fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {\n match self {\n MyError::Io(e) => write!(f, "Erreur d'E/S : {}", e),\n MyError::Parse(e) => write!(f, "Erreur d'analyse : {}", e),\n }\n }\n}\n\nimpl std::error::Error for MyError {}\n\n// Implémentons From pour une conversion automatique\nimpl From<io::Error> for MyError {\n fn from(error: io::Error) -> Self {\n MyError::Io(error)\n }\n}\n\nimpl From<std::num::ParseIntError> for MyError {\n fn from(error: std::num::ParseIntError) -> Self {\n MyError::Parse(error)\n }\n}\n\nfn process_data(path: &str) -> Result<i32, MyError> {\n let content = std::fs::read_to_string(path)?; // Converti automatiquement en MyError::Io\n let number: i32 = content.trim().parse()?; // Converti automatiquement en MyError::Parse\n Ok(number)\n}\n\nMaintenant, la fonction process_data retourne un type unique MyError, et l'opérateur ? convertit automatiquement les erreurs grâce au trait implémenté.