Result et la gestion des erreurs
Rust n’a pas d’exceptions. Une opération susceptible d’échouer renvoie un Result<T, E>, que le compilateur oblige à traiter. Cette page traite du versant « échec » ; l’absence de valeur, elle, relève d’Option.
Le type
Comme Option, c’est une simple énumération de la bibliothèque standard, annotée #[must_use] : ignorer un Result déclenche un avertissement.
Le partage des rôles entre les deux types tient à une question : l’appelant a-t-il besoin de savoir pourquoi ?
Option<T> | Result<T, E> | |
|---|---|---|
| Sémantique | L’absence est normale et attendue | Quelque chose a échoué |
| Le pourquoi importe | Non | Oui |
| Exemples | HashMap::get, Iterator::next | Entrées/sorties, analyse syntaxique, validation |
Les combinateurs
L’essentiel de l’interface reprend celle d’Option, avec une couche d’erreur en plus :
map_err est le plus spécifique des deux mondes : c’est lui qui adapte une erreur de bibliothèque au type d’erreur de votre application.
L’opérateur ?
? extrait la valeur en cas de succès, et retourne immédiatement l’erreur sinon.
Sa vraie puissance tient à ce qu’il ne se contente pas de propager : il convertit l’erreur au passage, en appelant From::from. Dès lors qu’une conversion existe du type d’erreur rencontré vers celui déclaré par la fonction, ? l’applique tout seul.
Les deux appels renvoient des erreurs de types différents, et pourtant un seul ? suffit dans chaque cas — à condition d’avoir fourni les implémentations From correspondantes.
Warning
? ne mélange pas Option et Result au sein d’une même fonction. Les passerelles sont explicites : ok_or_else pour aller d’Option vers Result, .ok() pour l’inverse.
Définir son type d’erreur
Une énumération recense les modes de défaillance du domaine :
Pour qu’elle s’intègre à l’écosystème, deux implémentations sont attendues. Display fournit le message destiné à l’utilisateur :
std::error::Error marque le type comme une erreur et expose la cause sous-jacente, ce qui permet aux outils de reconstituer la chaîne complète :
Enfin, les implémentations From sont ce qui rend l’opérateur ? transparent :
Tip
Ce code est mécanique et se répète pour chaque variante. C’est précisément ce que les crates de la section suivante automatisent.
Box<dyn Error>
Quand la nature exacte de l’erreur importe peu — un prototype, un binaire, un script — Box<dyn Error> accepte n’importe quelle erreur, puisqu’une implémentation générale de la bibliothèque standard convertit tout type Error vers cette boîte.
Aucune implémentation From à écrire, mais l’appelant perd la capacité de distinguer les cas par match. À réserver aux frontières du programme, jamais au cœur d’une bibliothèque.
thiserror et anyhow
Deux crates complémentaires, écrites par le même auteur, couvrent les deux situations.
thiserror engendre Display, Error et les From à partir d’annotations. C’est le choix pour une bibliothèque, où l’appelant doit pouvoir discriminer les erreurs.
Ces quinze lignes remplacent la soixantaine de la section précédente, pour un résultat identique.
anyhow fournit un type d’erreur unique et opaque, enrichi de contexte. C’est le choix pour une application, où l’erreur finit journalisée ou affichée.
context empile les explications, ce qui produit à l’affichage une trace lisible du plus général au plus précis, là où l’erreur brute d’origine n’aurait mentionné qu’un fichier introuvable.
thiserror | anyhow | |
|---|---|---|
| Destination | Bibliothèques | Applications, binaires |
| Type d’erreur | Énumération explicite | Type opaque unique |
| L’appelant peut discriminer | Oui | Difficilement |
| Apport principal | Supprime le code répétitif | Ajoute du contexte |
Paniquer ou retourner une erreur
panic! interrompt le fil d’exécution ; il n’est pas un mécanisme de gestion d’erreurs. Le critère de choix est simple :
| Situation | Réponse |
|---|---|
| L’échec est prévisible et l’appelant peut réagir | Result |
| Un invariant du programme est rompu | panic! |
| Entrée extérieure invalide | Result — jamais une panique |
| Bogue de programmation | panic!, unreachable!, assert! |
Un fichier absent, un JSON mal formé, un port déjà pris sont des situations attendues : ce sont des Result. Un index hors bornes sur une structure interne ou un état déclaré impossible sont des bogues : la panique est alors le bon signal, parce qu’elle échoue tôt et bruyamment.
Warning
Une bibliothèque ne devrait presque jamais paniquer sur une entrée fournie par son appelant : cela lui retire tout choix. La panique est un privilège de l’application, qui décide en dernier ressort de ce qui est irrécupérable.
unwrap et expect
Le message d’expect doit expliquer pourquoi l’erreur est impossible ici, et non ce qui a échoué — cette dernière information figure déjà dans l’erreur. unwrap() nu se justifie dans les tests, les prototypes et les exemples, où l’échec doit interrompre immédiatement.
Result dans main
main peut retourner un Result, ce qui autorise l’usage de ? dès le point d’entrée. En cas d’erreur, le programme affiche la représentation Debug de celle-ci et rend un code de sortie non nul.
Note
C’est bien Debug et non Display qui est employé pour cet affichage — d’où l’intérêt de dériver Debug sur tout type d’erreur. Pour maîtriser exactement le message, mieux vaut traiter l’erreur dans main et appeler std::process::exit soi-même.
Result et les itérateurs
collect() sait rassembler un itérateur de Result en un Result de collection, en court-circuitant à la première erreur :
Pour au contraire ignorer les échecs, filter_map fait l’affaire, et partition permet de conserver les deux côtés :
Ces mécanismes sont détaillés dans Les itérateurs.
À retenir
Resultpour un échec dont l’appelant doit connaître la cause,Optionpour une absence attendue.?propage et convertit viaFrom: c’est ce qui rend viable un type d’erreur unique par module.thiserrorpour une bibliothèque,anyhowpour une application.panic!signale un bogue, jamais une entrée invalide.- Le message d’
expectexplique pourquoi l’erreur ne peut pas survenir.
Ressources
- Error Handling — chapitre du Rust Book
std::error::Error— le trait et sa méthodesourcethiserroretanyhow— les deux crates de référence- Rust API Guidelines — erreurs — les attentes sur un type d’erreur public