4. Gestion des erreurs : Result, Option et l’opérateur ?

La gestion des erreurs en Rust est explicite et idiomatique : pas d’exceptions cachées, pas de valeurs null inattendues. Rust distingue deux catégories d’erreurs — les erreurs irrécupérables (panics) et les erreurs récupérables (Result) — et fournit des outils élégants pour les gérer sans verbosité excessive. Dans cette unité, vous apprendrez à écrire du code qui gère les erreurs de façon propre et lisible.

 

Panic ! : les erreurs irrécupérables

Un panic est une erreur fatale : le programme s’arrête immédiatement avec un message d’erreur et une trace de la pile. On l’utilise pour les invariants violés, les bugs de programmation, jamais pour les erreurs prévisibles.

fn diviser(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("Division par zero !");  // arrêt immédiat
    }
    a / b
}

// Accès hors limites
let v = vec![1, 2, 3];
// v[10];  // thread 'main' panicked: index out of bounds

 

Result<T, E> : les erreurs récupérables

Result est un enum à deux variantes : Ok(valeur) en cas de succès, et Err(erreur) en cas d’échec. Toute fonction pouvant échouer retourne un Result.

use std::fs;
use std::io;

fn lire_fichier(chemin: &str) -> Result<String, io::Error> {
    let contenu = fs::read_to_string(chemin)?;  // ? propage l'erreur
    Ok(contenu)
}

fn main() {
    match lire_fichier("config.txt") {
        Ok(contenu) => println!("Contenu : {}", contenu),
        Err(e) => println!("Erreur : {}", e),
    }
}

L’opérateur ? : propagation élégante

L’opérateur ? est du sucre syntaxique pour le pattern match sur Result : si c’est Ok, il extrait la valeur ; si c’est Err, il retourne immédiatement l’erreur. Il transforme le code verbeux en code lisible.

// Sans ? (verbeux)
fn lire_v1(chemin: &str) -> Result<u32, io::Error> {
    let contenu = match fs::read_to_string(chemin) {
        Ok(c) => c,
        Err(e) => return Err(e),
    };
    Ok(contenu.len() as u32)
}

// Avec ? (idiomatique)
fn lire_v2(chemin: &str) -> Result<u32, io::Error> {
    let contenu = fs::read_to_string(chemin)?;
    Ok(contenu.len() as u32)
}

 

Option<T> : les valeurs absentes

Option est l’alternative de Rust à null. Une valeur de type Option<T> est soit Some(valeur), soit None. Le compilateur force la gestion des deux cas, éliminant les NullPointerExceptions.

fn trouver_prefixe(reference: &str) -> Option<&str> {
    if reference.contains("DOC") {
        Some(reference.split("--").next().unwrap_or(reference))
    } else {
        None
    }
}

fn main() {
    let r = "DOC-2024-001";
    match trouver_prefixe(r) {
        Some(p) => println!("Préfixe : {}", p),
        None    => println!("Pas de préfixe"),
    }

    // Méthodes utilitaires
    let valeur = trouver_prefixe("DOC-001").unwrap_or("INCONNU");
    let majuscule = trouver_prefixe("DOC-001").map(|p| p.to_uppercase());
    println!("{} / {:?}", valeur, majuscule);
}

 

Créer ses types d’erreur

Pour les projets plus importants, on crée ses propres types d’erreur avec un enum et on implémente le trait std::error::Error. La crate thiserror simplifie cette tâche :

use thiserror::Error;

#[derive(Error, Debug)]
enum ErreurRef {
    #[error("Format invalide : {0}")]
    FormatInvalide(String),
    #[error("Référence non trouvée")]
    NonTrouvee,
    #[error("Erreur I/O : {0}")]
    Io(#[from] std::io::Error),
}

fn analyser_ref(s: &str) -> Result<u32, ErreurRef> {
    if s.is_empty() {
        return Err(ErreurRef::FormatInvalide(s.to_string()));
    }
    s.parse::<u32>().map_err(|_| ErreurRef::FormatInvalide(s.to_string()))
}
Points clés :

  • panic! pour les bugs irrécupérables, Result pour les erreurs prévisibles
  • L’opérateur ? propage les erreurs élégamment dans les fonctions qui retournent Result
  • Option<T> remplace null et force la gestion du cas absent à la compilation
  • Créez vos propres types d’erreur avec thiserror pour un code expressif
SEE ALL Add a note
YOU
Add your Comment