5.2 KiB
Gestion des erreurs
Ce document décrit comment les erreurs sont gérées dans le projet.
Gestion native
Par principe, en Rust, on évite au maximum la gestion par exception, ne la réservant qu'aux situations où un crash du programme est la meilleure solution.
En temps normal, on renvoie des Result<Valeur, Erreur>
(pour les situations réussite/erreur) ou des Option<Valeur>
(pour les situations valeur non-nulle/nulle).
Quand on fait face à une situation d'erreur, on cherchera à la gérer de manière explicite (voir Récupération des erreurs) ou à la remonter à un niveau supérieur, généralement à l'aide de l'opérateur ?
.
On évitera, par contre, au maximum de générer des exceptions (appelées "panics" en Rust), que ce soit par l'usage de panic!
ou par des appels à des fonctions qui paniquent en cas d'erreur (comme unwrap
ou expect
).
De nombreux exemples des idiomes natifs de gestion des erreurs en Rust sont disponibles dans la documentation Rust by example.
Librairies de gestion des erreurs
Deux librairies sont utilisées pour gérer les erreurs dans le projet :
anyhow
: qui permet de renvoyer des erreurs faiblement typées, mais très facile à enrichir avec des messages d'explication. On l'utilise pour communiquer facilement des erreurs de haut niveau avec un utilisateur final.use anyhow::{anyhow, Result}; fn get_cluster_info() -> Result<ClusterInfo> { let data = fs::read_to_string("cluster.json") .with_context(|| "failed to read cluster config")?; let info: ClusterInfo = serde_json::from_str(&data) .with_context(|| "failed to parse cluster config")?; Ok(info) }
thiserror
: qui fournit des macros pour définir des erreurs fortement typées. On l'utilise pour définir des erreurs spécifiques à une partie du code, contextualisées avec des données structurées plutôt que de simples messages d'erreurs, afin de favoriser la "récupération" face aux erreurs.use thiserror::Error; #[derive(Error, Debug)] pub enum DataStoreError { #[error("data store disconnected")] Disconnect(#[from] io::Error), #[error("the data for key `{0}` is not available")] Redaction(String), #[error("invalid header (expected {expected:?}, found {found:?})")] InvalidHeader { expected: String, found: String, }, #[error("unknown data store error")] Unknown, }
Récupération des erreurs
Dans la mesure du possible, on essaie de privilégier la "récupération" face à une erreur plutôt que le "crash". Les stratégies de récupération sont :
- Réessayer l'opération, tel quel ou avec des paramètres différents
- Contourner l'opération, en la remplaçant par une autre
- À défaut, informer l'utilisateur de l'erreur et :
- arrêter / annuler l'opération en cours
- ignorer l'erreur et continuer l'exécution
Quand on ne peut pas récupérer une erreur, on la remonte à un niveau supérieur, si besoin en la convertissant dans un type d'erreur plus générique et approprié au niveau considéré.
Conversion des erreurs
Quand on remonte une erreur à un niveau supérieur, on peut être amené à la convertir dans un type d'erreur plus générique et approprié au niveau considéré. Pour faciliter cette conversion, on implémente le trait From
. Avec thiserror
, on peut utiliser l'attribut #[from]
ou le paramètre source
pour automatiser l'implémentation de telles conversions.
On peut ensuite, lors de la gestion d'une erreur, on pourra :
- soit directement renvoyer l'erreur à l'aide de l'opérateur
?
, qui se chargera de la conversion ; - soit convertir l'erreur explicitement, par exemple en utilisant la méthode
map_err
sur unResult
, en particulier quand on veut enrichir l'erreur avec des informations supplémentaires.
Usages exceptionnels de unwrap
et expect
Provoquant des "panics" en cas d'erreur, les fonctions unwrap
et expect
ne doivent être utilisées que dans des cas exceptionnels :
- Dans les tests, pour signaler une erreur de test
- Au plus haut niveau de l'application, pour signaler une erreur fatale qui ne peut pas être récupérée
- Dans des situations où l'erreur ne peut pas se produire, par exemple après une vérification de préconditions
Dans l'idéal, on préférera l'usage de expect
à unwrap
, car il permet de donner un message d'erreur explicite.