3. Gestion d’Erreurs — Option, Result, ?

Rust n’a pas d’exceptions. Les erreurs sont gérées via les types Option<T> (absence de valeur) et Result<T, E> (échec récupérable). Le pattern matching et l’opérateur ? rendent la gestion d’erreur explicite et concise.


3.1 Option<T> — Quand une valeur peut être absente

Option<T> remplace null / None des autres langages. Le compilateur vous oblige à traiter le cas None.

fn premiere_ligne(texte: &str) -> Option<&str> {
    texte.lines().next()
}
 
let texte = "hello\nworld";
match premiere_ligne(texte) {
    Some(ligne) => println!("première ligne : {ligne}"),
    None => println!("texte vide"),
}

Méthodes utiles sur Option

let x: Option<i32> = Some(42);
 
// Extraction avec valeur par défaut
x.unwrap_or(0);          // 42 si Some, 0 si None
x.unwrap_or_else(|| 0);  // lazy : closure
 
// Transformation
x.map(|v| v * 2);                    // Some(84) si Some, None si None
x.and_then(|v| Some(v + 1));          // flat map
x.filter(|v| v > 50);                 // Some si prédicat vrai, sinon None
 
// Extraction directe (⚠️ panique si None)
x.unwrap();               // 42 — panique si None
x.expect("message");      // 42 — message personnalisé si None
 
// Combinators
let y: Option<i32> = None;
x.or(y);                   // Some(42) (priorité à x)
x.and(y);                  // None (y est None)

3.2 Result<T, E> — Quand une opération peut échouer

Result<T, E> est le type de retour standard pour les opérations qui peuvent échouer de façon récupérable.

use std::fs::File;
use std::io::Read;
 
fn lire_fichier(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?;         // ? propage l'erreur
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;      // ? propage
    Ok(contents)
}

Créer ses propres types d’erreur

use std::fmt;
 
#[derive(Debug)]
enum GradientError {
    InvalidShape { expected: usize, got: usize },
    EmptyGradients,
    CudaError(String),
}
 
impl fmt::Display for GradientError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidShape { expected, got } => {
                write!(f, "shape invalide: attendu {expected}, reçu {got}")
            }
            Self::EmptyGradients => write!(f, "liste de gradients vide"),
            Self::CudaError(msg) => write!(f, "erreur CUDA: {msg}"),
        }
    }
}
 
impl std::error::Error for GradientError {}  // trait Error
 
// Utilisation
fn agreger_gradients(grads: &[Vec<f64>]) -> Result<Vec<f64>, GradientError> {
    if grads.is_empty() {
        return Err(GradientError::EmptyGradients);
    }
    let d = grads[0].len();
    for g in grads {
        if g.len() != d {
            return Err(GradientError::InvalidShape { expected: d, got: g.len() });
        }
    }
    Ok(grads.iter().map(|g| g.iter().sum::<f64>() / grads.len() as f64).collect())
}

3.3 L’Opérateur ? — Propagation concise

L’opérateur ? est du sucrage syntaxique pour un match immédiat :

// Avec match explicite
fn read_file_match(path: &str) -> Result<String, std::io::Error> {
    let mut file = match File::open(path) {
        Ok(f) => f,
        Err(e) => return Err(e),
    };
    let mut contents = String::new();
    match file.read_to_string(&mut contents) {
        Ok(_) => Ok(contents),
        Err(e) => Err(e),
    }
}
 
// Avec ?
fn read_file_question(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

? avec Option

? fonctionne aussi avec Option :

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
    //                 ↑ si None, return None immédiatement
}

? dans main

Depuis Rust 1.26, main peut retourner Result :

use std::fs::File;
 
fn main() -> Result<(), std::io::Error> {
    let f = File::open("config.toml")?;
    Ok(())
}

3.4 unwrap et expect — À utiliser avec parcimonie

Ces méthodes paniquent si la valeur est None/Err. À réserver aux :

  • Prototypes / tests
  • Cas où on est certain que la valeur existe
  • Erreurs fatales irrécupérables (ex. CONFIG doit être lisible)
// Acceptable (prototype)
fn main() {
    let args: Vec<String> = std::env::args().collect();
    let path = args.get(1).expect("Usage: mon_prog <fichier>");
    let data = std::fs::read_to_string(path).unwrap();
}
 
// Meilleur (production)
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<String> = std::env::args().collect();
    let path = args.get(1).ok_or("Usage: mon_prog <fichier>")?;
    let data = std::fs::read_to_string(path)?;
    Ok(())
}

3.5 Combinaisons Fréquentes

Option et Result ensemble

fn parse_gradient(s: &str) -> Option<f64> {
    s.parse().ok()  // Result → Option via .ok()
}
 
fn process(grads: &[&str]) -> Result<Vec<f64>, String> {
    grads.iter()
        .map(|s| parse_gradient(s).ok_or_else(|| format!("échec parse: {s}")))
        .collect()
}

Chaînage idiomatique

fn median(grads: &[f64]) -> Option<f64> {
    if grads.is_empty() { return None; }
    let mut sorted = grads.to_vec();
    sorted.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
    let mid = sorted.len() / 2;
    Some(if sorted.len() % 2 == 0 {
        (sorted[mid - 1] + sorted[mid]) / 2.0
    } else {
        sorted[mid]
    })
}
 
// Usage chaîné
fn compute_trimmed_mean(grads: &[f64]) -> Result<f64, String> {
    let n = grads.len();
    if n < 4 { return Err("besoin d'au moins 4 gradients".into()); }
    let trim = n / 4;
    let trimmed: Vec<_> = grads.iter().copied()
        .skip(trim)
        .take(n - 2 * trim)
        .collect();
    let sum: f64 = trimmed.iter().sum();
    Ok(sum / trimmed.len() as f64)
}

3.6 Résumé

SituationTypePropagation
Valeur optionnelleOption<T>?, .unwrap_or(), .map()
Opération faillibleResult<T, E>?, .ok(), .or_else()
Erreur fatalepanic!()unwrap(), expect()
Erreur dans mainResult<(), Box<dyn Error>>?

Règle d’or : Si l’erreur peut être anticipée (fichier manquant, parse invalide), utilisez Result. Si c’est un bug (index hors limites), utilisez panic! / expect.


🔗 Voir aussi