web-dev-qa-db-fra.com

Rust bonne gestion des erreurs (conversion automatique d'un type d'erreur à un autre avec un point d'interrogation)

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!!!
} 
18
S.R

Mise à jour 10.01.2020

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 ?.

sources: crates.io ou github


Autres caisses intéressantes:

  • de toute façon - Béton flexible Type d'erreur construit sur std :: error :: Error
  • snaf - Situation normale: tous encrassés - SNAFU est une bibliothèque pour attribuer facilement des erreurs sous-jacentes à des erreurs spécifiques au domaine tout en ajoutant du contexte. (similaire à cette erreur)
  • custom_error - Cette caisse contient une macro qui devrait faciliter la définition des erreurs personnalisées sans avoir à écrire beaucoup de code passe-partout .

pour les paniques:

  • proc-macro-error - Cette caisse vise à rendre le rapport d'erreurs dans proc-macros simple et facile à utiliser.
  • human-panic - Messages de panique pour les humains. Gère les paniques en appelant std :: panic :: set_hook pour faire des erreurs Nice pour les humains.
4
S.R

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:

  1. 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.

  2. Utilisez un type d'erreur générique spécial. Il y en a essentiellement deux:

    une. Box<Error>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:

  1. Définissez le type Result:

    type Result<T> = std::result::Result<T, failure::Error>;
    
  2. 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.

17
Vladimir Matveev

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"
}
4
lovasoa

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.

1
Nightsneaker