Je veux apprendre à gérer correctement les erreurs dans Rust. J'ai lu les livre et cet exemple ; maintenant je voudrais savoir comment traiter les erreurs dans cette fonction:
fn get_synch_point(&self) -> Result<pv::synch::MeasPeriods, reqwest::Error> {
let url = self.root.join("/term/pv/synch"); // self.root is url::Url
let url = match url {
Ok(url) => url,
// ** this err here is url::ParseError and can be converted to Error::Kind https://docs.rs/reqwest/0.8.3/src/reqwest/error.rs.html#54-57 **//
Err(err) => {
return Err(Error {
kind: ::std::convert::From::from(err),
url: url.ok(),
})
}
};
Ok(reqwest::get(url)?.json()?) //this return reqwest::Error or convert to pv::sych::MeasPeriods automaticly
}
Ce code est incorrect; cela provoque une erreur de compilation:
error[E0451]: field `kind` of struct `reqwest::Error` is private
--> src/main.rs:34:42
|
34 | Err(err) => return Err(Error{kind: ::std::convert::From::from(err), url: url.ok()})
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ field `kind` is private
error[E0451]: field `url` of struct `reqwest::Error` is private
--> src/main.rs:34:81
|
34 | Err(err) => return Err(Error{kind: ::std::convert::From::from(err), url: url.ok()})
| ^^^^^^^^^^^^^ field `url` is private
Quel est le modèle approprié pour traiter ce cas? Pour moi, reqwest::Error
dans ce cas est une bonne solution donc je voudrais éviter de définir mon propre type d'erreur:
enum MyError {
Request(reqwest::Error),
Url(url::ParseError) // this already a part of request::Error::Kind!!!
}
Le langage de programmation Rust évolue rapidement, donc une nouvelle réponse peut être ajoutée! J'ai vraiment aimé custom_error mais maintenant je pense que thiserror
sera mon être cher !
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[source] 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,
}
Cela permet le changement io::Error
à DataStoreError::Disconnect
avec point d'interrogation ?
.
Malheureusement, dans votre cas, vous ne pouvez pas créer un reqwest::Error
à partir d'autres types d'erreur, si la bibliothèque reqwest
ne fournit pas un moyen de le faire (et ce n'est probablement pas le cas). Pour résoudre ce problème, qui est très courant, en particulier dans les applications qui utilisent plusieurs bibliothèques, la solution appropriée serait l'une des suivantes:
Déclarez votre propre énumération personnalisée avec toutes les erreurs avec lesquelles votre application fonctionne (ou un sous-système de votre application; la granularité dépend fortement du projet) et déclarez From
conversions de toutes les erreurs avec lesquelles vous travaillez vers ce type d'énumération.
Dans le prolongement de cette approche, vous pouvez utiliser error-chain
(ou quick-error
, sur lequel la chaîne d'erreurs est essentiellement basée) pour générer ces types et conversions personnalisés de manière semi-automatique.
Utilisez un type d'erreur générique spécial. Il y en a essentiellement deux:
une. Box<Error>
où Error
est défini dans la bibliothèque standard.
b. Utilisez le type Error
défini dans la caisse failure
.
L'opérateur de point d'interrogation pourra alors convertir toute erreur compatible en l'un de ces types en raison de diverses implémentations de traits Into
et From
.
Notez que la caisse failure
est destinée à être la façon de définir les erreurs promues dans la Rust Non seulement il fournit un type d'erreur et un trait communs (qui corrigent divers problèmes avec le std::error::Error
trait; voir par exemple ici ), il a également la possibilité de définir vos propres types d'erreur (par exemple, avec failure_derive
), et pour le suivi du contexte d'erreur, les causes et la génération de backtrace. De plus, il essaie d'être aussi compatible que possible avec les approches de gestion des erreurs existantes, il peut donc être utilisé pour s'intégrer à des bibliothèques qui utilisent d'autres approches plus anciennes (std::error::Error
, error-chain
, quick-error
) plutôt facilement. Je vous suggère donc fortement d'envisager d'utiliser cette caisse en premier, avant les autres options.
J'ai déjà commencé à utiliser failure
dans mes projets d'application, et je ne peux tout simplement pas exprimer à quel point la gestion des erreurs est devenue plus facile et plus agréable. Mon approche est la suivante:
Définissez le type Result
:
type Result<T> = std::result::Result<T, failure::Error>;
Utilisation Result<Something>
partout où une erreur peut être renvoyée, à l'aide de l'opérateur de point d'interrogation (?
) pour convertir entre des erreurs et des fonctions comme err_msg
ou format_err!
ou bail!
pour créer mes propres messages d'erreur.
Je n'ai pas encore écrit de bibliothèque en utilisant failure
, mais j'imagine que pour les bibliothèques, il serait important de créer des erreurs plus spécifiques déclarées comme une énumération, ce qui peut être fait avec le failure_derive
Caisse. Pour les applications, cependant, le failure::Error
le type est plus que suffisant.
Dans ce cas, la réutilisation du type d'erreur sous-jacent n'est pas possible car vous ne pouvez pas construire ses champs masqués. Et même lorsque cela est possible, je vous déconseille, afin de rendre votre code plus flexible et évolutif.
La définition de types d'erreurs personnalisés peut impliquer l'écriture de beaucoup de passe-partout, mais heureusement, plusieurs bibliothèques existent pour atténuer cette douleur. échec, chaîne d'erreur et erreur rapide ont déjà été mentionnés ci-dessus, mais je voudrais vous signaler un caisse j'ai écrit qui implique encore moins de passe-partout que les autres: custom_error . Avec lui, vous pouvez écrire:
#[macro_use] extern crate custom_error;
custom_error!{ MyError
Request{source: reqwest::Error} = "request error",
Url{source: url::ParseError} = "invalid url"
}
Comme déjà déclaré par Vladimir Matveev , la caisse échec devrait être votre point de départ. Voici ma solution:
use std::io;
use std::result;
use failure::{Backtrace, Fail};
/// This is a new error type manged by Oxide library.
/// The custom derive for Fail derives an impl of both Fail and Display.
#[derive(Debug, Fail)]
pub enum OxideError {
#[fail(display = "{}", message)]
GeneralError { message: String },
#[fail(display = "{}", message)]
IoError {
message: String,
backtrace: Backtrace,
#[cause]
cause: io::Error,
},
}
/// Create general error
pub fn general(fault: &str) -> OxideError {
OxideError::GeneralError {
message: String::from(fault),
}
}
/// Create I/O error with cause and backtrace
pub fn io(fault: &str, error: io::Error) -> OxideError {
OxideError::IoError {
message: String::from(fault),
backtrace: Backtrace::new(),
cause: error,
}
}
Cette énumération d'erreurs est extensible, ce qui lui permet de s'adapter aux futures modifications qui pourraient être apportées au programme.