Gestion des erreurs en Rust : abandon de try/catch au profit de la sécurité
Contrairement à Python ou Java, Rust ne possède pas de construction classique try/catch. Les développeurs du langage ont emprunté une autre voie, en faisant de la gestion des erreurs une partie du système de types. Ce n'est pas un caprice, mais une décision fondamentale qui permet de garantir la sécurité mémoire et la prévisibilité du programme dès la compilation. Dans cet article, nous allons passer en revue toutes les méthodes de gestion des erreurs en Rust : des Result et Option de base aux méthodes avancées avec l'opérateur ? et les types d'erreurs personnalisés.
1. Deux types principaux : Result et Option
En Rust, toutes les erreurs se divisent en deux catégories : les récupérables (traitées via Result) et les irrécupérables (conduisant à panic!). Il existe également le type Option pour les cas où une valeur peut être absente.
Le type Result
Result<T, E> est une énumération (enum) qui peut être soit Ok(T) (succès avec des données), soit Err(E) (erreur avec une description). C'est l'outil principal pour traiter les erreurs attendues : erreurs d'entrée-sortie, d'analyse, de requêtes réseau.
use std::fs::File;use std::io::Read;
fn read_file_content(path: &str) -> Result<String, std::io::Error> { let mut file = File::open(path)?; let mut content = String::new(); file.read_to_string(&mut content)?; Ok(content)}Le type Option
Option<T> est une autre énumération qui prend les valeurs Some(T) (valeur présente) ou None (valeur absente). Elle est utilisée lorsque l'absence de données est une situation normale, et non une erreur.
fn find_user(id: u32) -> Option<String> { let users = vec!["Alice".to_string(), "Bob".to_string()]; users.get(id as usize).cloned()}
fn main() { match find_user(0) { Some(name) => println!("Utilisateur trouvé : {}", name), None => println!("Utilisateur non trouvé"), }}2. L'opérateur ? — une élégante alternative à try/catch
Si Rust avait un try/catch, il serait lourd. À la place, le langage propose l'opérateur ?. Il fonctionne ainsi : si le résultat est Ok, la valeur est extraite et transmise ; si c'est Err, l'erreur est immédiatement retournée depuis la fonction.
use std::num::ParseIntError;
fn parse_and_double(input: &str) -> Result<i32, ParseIntError> { let value: i32 = input.parse()?; // si erreur — return Err immédiat Ok(value * 2)}
fn main() { match parse_and_double("42") { Ok(result) => println!("Résultat : {}", result), Err(e) => println!("Erreur d'analyse : {}", e), }}L'opérateur ? ne peut être utilisé que dans les fonctions qui retournent Result ou Option. Cela oblige le développeur à indiquer explicitement que la fonction peut échouer — contrairement aux exceptions en Python qui peuvent « surgir » de n'importe où.
3. Quand utiliser panic! et unwrap
Toutes les erreurs ne doivent pas être traitées. panic! est une macro pour les erreurs irrécupérables. Elle arrête le programme et déroule la pile (stack unwind). Utilisez-la uniquement dans des situations exceptionnelles :
- États d'urgence : violation d'invariants impossibles à corriger.
- Exemples et prototypes : dans du code pédagogique ou des scripts jetables.
- Tests : pour vérifier que le code plante aux endroits attendus.
fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("Division par zéro ! a={}, b={}", a, b); } a / b}unwrap() et expect()