J'écris une fonction qui pourrait renvoyer plusieurs erreurs différentes.
fn foo(...) -> Result<..., MyError> {}
J'aurai probablement besoin de définir mon propre type d'erreur pour représenter ces erreurs. Je suppose que ce serait une enum
d'erreurs possibles, certaines variantes de enum
étant associées à des données de diagnostic:
enum MyError {
GizmoError,
WidgetNotFoundError(widget_name: String)
}
Est-ce la façon la plus idiomatique de s'y prendre? Et comment puis-je implémenter le trait Error
?
Vous implémentez Error
exactement comme vous le feriez tout autre trait ; il n'y a rien de très spécial à ce sujet:
pub trait Error: Debug + Display {
fn description(&self) -> &str { /* ... */ }
fn cause(&self) -> Option<&Error> { /* ... */ }
fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}
description
, cause
et source
ont tous des implémentations par défaut1et votre type doit également implémenter Debug
et Display
, car il s’agit de supertraits.
use std::{error::Error, fmt};
#[derive(Debug)]
struct Thing;
impl Error for Thing {}
impl fmt::Display for Thing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Oh no, something bad went down")
}
}
Bien sûr, ce que contient Thing
, et donc l’implémentation des méthodes, dépend fortement du type d’erreur que vous souhaitez commettre. Peut-être que vous souhaitez inclure un nom de fichier, ou peut-être un entier. Peut-être voudriez-vous avoir une enum
au lieu de struct
pour représenter plusieurs types d’erreurs.
Si vous finissez par envelopper les erreurs existantes, nous vous recommandons d'implémenter From
pour convertir ces erreurs en votre erreur. Cela vous permet d’utiliser try!
et ?
et d’avoir une jolie solution ergonomique.
Est-ce la façon la plus idiomatique de s'y prendre?
Idéalement, je dirais qu'une bibliothèque aura un petit nombre (peut-être 1 à 3) de types d'erreur principaux exposés. Celles-ci sont probablement des énumérations d'autres types d'erreur. Cela permet aux consommateurs de votre caisse de ne pas faire face à une explosion de types. Bien sûr, cela dépend de votre API et de la pertinence de regrouper certaines erreurs ou non.
Une autre chose à noter est que, lorsque vous choisissez d'incorporer des données dans l'erreur, cela peut avoir de lourdes conséquences. Par exemple, la bibliothèque standard n'inclut pas de nom de fichier dans les erreurs liées au fichier. Cela ajouterait une surcharge à chaque erreur de fichier. L'appelant de la méthode a généralement le contexte approprié et peut décider si ce contexte doit être ajouté à l'erreur ou non.
Je recommanderais de le faire à la main plusieurs fois pour voir comment toutes les pièces vont ensemble. Une fois que vous avez cela, vous en aurez assez de le faire manuellement. Ensuite, vous pouvez consulter des caisses telles que quick-error , error-chain ou failure , qui fournissent des macros permettant de réduire le passe-partout.
Ma bibliothèque préférée est quick-error, alors voici un exemple d'utilisation avec le type d'erreur d'origine:
#[macro_use]
extern crate quick_error;
quick_error! {
#[derive(Debug)]
enum MyError {
Gizmo {
description("Refrob the Gizmo")
}
WidgetNotFound(widget_name: String) {
description("The widget could not be found")
display(r#"The widget "{}" could not be found"#, widget_name)
}
}
}
fn foo() -> Result<(), MyError> {
Err(MyError::WidgetNotFound("Quux".to_string()))
}
fn main() {
println!("{:?}", foo());
}
Remarque J'ai supprimé le suffixe redondant Error
sur chaque valeur enum. Il est également courant d'appeler simplement le type Error
et de permettre au consommateur de préfixer le type (mycrate::Error
) ou de le renommer à l'importation (use mycrate::Error as FooError
).
1 Avant que RFC 2504 soit implémenté, description
était une méthode obligatoire.
Est-ce la façon la plus idiomatique de s'y prendre? Et comment puis-je implémenter le trait Erreur?
C'est un moyen commun, oui. "idiomatique" dépend de la force avec laquelle vous voulez que vos erreurs soient, et de la façon dont vous voulez que cela interagisse avec d'autres choses.
Et comment puis-je implémenter le trait Erreur?
Strictement parlant, vous n’avez pas besoin d’être ici. Vous pouvez éventuellement assurer l’interopérabilité avec d’autres éléments nécessitant Error
, mais puisque vous avez défini votre type de retour directement sous cette énumération, votre code doit fonctionner sans lui.
La caisse custom_error permet la définition de types d'erreur personnalisés avec moins de passe-passe que ce qui était proposé ci-dessus:
custom_error!{MyError
Io{source: io::Error} = "input/output error",
WidgetNotFoundError{name: String} = "could not find widget '{name}'",
GizmoError = "A gizmo error occurred!"
}
Disclaimer: Je suis l'auteur de cette caisse.