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) etResult<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.
CONFIGdoit ê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é
| Situation | Type | Propagation |
|---|---|---|
| Valeur optionnelle | Option<T> | ?, .unwrap_or(), .map() |
| Opération faillible | Result<T, E> | ?, .ok(), .or_else() |
| Erreur fatale | panic!() | unwrap(), expect() |
Erreur dans main | Result<(), 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
- 1-Ownership et Bases
- 2-Types Structs Enums
- 5-Traits et Generics — implémenter
Error