【问题标题】:Rust proper error handling (auto convert from one error type to another with question mark)Rust 正确的错误处理(使用问号自动从一种错误类型转换为另一种错误类型)
【发布时间】:2018-07-03 23:47:51
【问题描述】:

我想学习如何正确处理 Rust 中的错误。我已经阅读了bookthis example;现在我想知道我应该如何处理这个函数中的错误:

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
}      

此代码不正确;它会导致编译错误:

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

处理这种情况的正确模式是什么?对我来说,reqwest::Error 在这种情况下是一个很好的解决方案,所以我想避免定义自己的错误类型:

enum MyError {
    Request(reqwest::Error),
    Url(url::ParseError) // this already a part of request::Error::Kind!!!
} 

【问题讨论】:

    标签: rust


    【解决方案1】:

    2020 年更新

    rust 编程语言发展迅速,因此可以添加新的答案!我真的很喜欢custom_error,但现在我想thiserror 会是我的挚爱!

    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,
    }
    

    这允许将io::Error 更改为DataStoreError::Disconnect,并带有问号?。去这里details

    有用的链接:


    其他有趣的箱子:

    • anyhow - 基于 std::error::Error 构建的灵活的具体错误类型
    • snafu - 情况正常:全部搞砸 - SNAFU 是一个库,可以在添加上下文的同时轻松地将基础错误分配到特定于域的错误中。 (类似这个错误)
    • custom_error - 这个 crate 包含一个,它可以更容易地定义自定义错误,而无需编写大量样板代码。

    对于恐慌:

    • proc-macro-error - 这个 crate 旨在让 proc-macros 中的错误报告变得简单易用。
    • human-panic - 人类的恐慌信息。通过调用 std::panic::set_hook 来处理恐慌,使错误对人类有益。

    【讨论】:

    • +1 表示thiserror,但快速更正:您使用#[from] 属性通过? 运算符获得自动转换,而不是#[source]#[from] 暗示)
    【解决方案2】:

    不幸的是,在您的情况下,如果 reqwest 库没有提供这样做的方法(而且很可能没有),您无法从其他错误类型创建 reqwest::Error。为了解决这个非常常见的问题,尤其是在使用多个库的应用程序中,正确的解决方案是以下之一:

    1. 声明您自己的自定义枚举,其中包含您的应用程序处理的所有错误(或应用程序的一个子系统;粒度高度取决于项目),并声明从您处理的所有错误到此枚举类型的From 转换。

      作为此方法的扩展,您可以使用error-chain(或quick-error,错误链基本上基于此)以半自动方式生成此类自定义类型和转换。

    2. 使用特殊的通用错误类型。基本上有两种:

      一个。 Box&lt;Error&gt; 其中Error 在标准库中定义。

      b.使用failure crate 中定义的Error 类型。

      然后问号运算符将能够将任何兼容的错误转换为这些类型之一,因为有各种 IntoFrom 特征实现。

    请注意,failure crate 旨在作为 在 Rust 社区中定义错误的方式。它不仅提供了一个常见的错误类型和特征(它修复了std::error::Error 特征的各种问题;参见例如here),它还具有定义您自己的错误类型的工具(例如,使用failure_derive) ,并用于跟踪错误上下文、原因和生成回溯。此外,它试图与现有的错误处理方法尽可能兼容,因此它可以很容易地与使用其他旧方法(std::error::Errorerror-chainquick-error)的库集成。所以我强烈建议你先考虑使用这个 crate,然后再使用其他选项。

    我已经开始在我的应用程序项目中使用failure,我无法表达错误处理变得多么容易和更好。我的做法如下:

    1. 定义Result 类型:

      type Result<T> = std::result::Result<T, failure::Error>;
      
    2. 在可以返回错误的任何地方使用Result&lt;Something&gt;,使用问号运算符 (?) 在错误和函数之间进行转换,例如 err_msgformat_err!bail! 以创建我自己的错误消息。

    我还没有使用failure 编写库,但我想对于库来说,创建声明为枚举的更具体的错误很重要,这可以使用failure_derive 板条箱来完成。不过,对于应用程序,failure::Error 类型绰绰有余。

    【讨论】:

    【解决方案3】:

    在这种情况下,无法重用底层错误类型,因为您无法构造其隐藏字段。即使有可能,我也建议不要这样做,以使您的代码更加灵活和面向未来。

    定义自定义错误类型可能涉及编写大量样板文件,但幸运的是,有几个库可以减轻这种痛苦。 failureerror-chainquick-error 已经在上面提到过,但我想指出我写的一个板条箱,它甚至涉及比其他样板少:custom_error。有了它,你可以写:

    #[macro_use] extern crate custom_error;
    
    custom_error!{ MyError
        Request{source: reqwest::Error} = "request error",
        Url{source: url::ParseError}    = "invalid url"
    }
    

    【讨论】:

    • 哦。谢谢那个箱子。我第一次使用失败,但现在我只想使用标准特征错误,这个宏超级简单!
    【解决方案4】:

    作为already stated by Vladimir Matveevfailure crate 应该是您的起点。这是我的解决方案:

    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,
        }
    }
    

    这个错误枚举是可扩展的,允许它适应未来可能对程序进行的修改。

    【讨论】:

    • 'failure' 已被弃用。 'anyhow'(更简单)和 'thiserror'(更精确,如库)是首选的现代替代方案。两者都是同一作者,可以一起使用。
    猜你喜欢
    • 2021-10-12
    • 2014-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    • 1970-01-01
    相关资源
    最近更新 更多