Rust正确的错误处理(自动从一种错误类型转换为另一种带问号的错误类型)

mqkwyuun  于 2023-01-09  发布在  其他
关注(0)|答案(5)|浏览(244)

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

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!!!
}
hsvhsicv

hsvhsicv1#

更新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
有用链接:

其他有趣的板条箱:

  • anyhow-在std::error::Error上构建的灵活具体错误类型
  • snafu-情况正常:SNAFU是一个库,在添加上下文时,可以轻松地将底层错误分配到特定于域的错误中。(类似于thiserror)
  • custom_error-此crate包含一个,该宏可以简化定义自定义错误的过程,而无需编写大量样板代码。

对于恐慌:

  • proc-macro-error-此crate旨在使proc宏中的错误报告简单易用。
  • human-panic-人类的恐慌消息。通过调用std::panic::set_hook来处理恐慌,以使错误对人类有利。
falq053o

falq053o2#

不幸的是,在您的情况下,如果reqwest库没有提供这样做的方法(很可能没有),您就不能从其他错误类型创建reqwest::Error。要解决这个问题,特别是在使用多个库的应用程序中,正确的解决方案是以下之一:
1.声明您自己的自定义枚举,其中包含应用程序处理的所有错误(或应用程序的一个子系统;粒度在很大程度上取决于项目),并声明从您处理的所有错误到该枚举类型的From转换。
作为这种方法的扩展,您可以使用error-chain(或quick-error,错误链基本上基于它)以半自动的方式生成这种自定义类型和转换。
1.使用特殊的、通用的错误类型。基本上有两种类型:
a. Box<Error>,其中Error在标准库中定义。
B.使用failure机箱中定义的Error类型。
然后,问号操作符将能够将任何兼容错误转换为这些类型之一,因为IntoFrom trait实现多种多样。
请注意,failure crate是用来定义Rust社区中出现的错误的,它不仅提供了一个通用的错误类型和trait(修复了std::error::Error trait的各种问题;例如,参见here),它还具有定义您自己的错误类型(例如,使用failure_derive)以及用于跟踪错误上下文、原因和生成回溯的工具。此外,它尝试尽可能与现有的错误处理方法兼容,因此它可用于与使用其他较旧方法的库集成(std::error::Errorerror-chainquick-error)相当容易。所以我强烈建议你先考虑使用这个板条箱,在其他选择之前。
我已经开始在我的应用程序项目中使用failure,我无法表达错误处理变得多么容易和好。
1.定义Result类型:

type Result<T> = std::result::Result<T, failure::Error>;

1.在任何可能返回错误的地方使用Result<Something>,使用问号运算符(?)在错误和err_msgformat_err!bail!等函数之间进行转换,以创建我自己的错误消息。
我还没有用failure写过一个库,但是我想对于库来说,创建更具体的错误声明为枚举是很重要的,这可以用failure_derive crate来完成,但是对于应用程序来说,failure::Error类型已经足够了。

soat7uwm

soat7uwm3#

在这种情况下,重用底层错误类型是不可能的,因为你无法构造它的隐藏字段。即使可以重用底层错误类型,我也建议不要重用,以便让你的代码更灵活,更经得起未来的考验。
定义自定义错误类型可能需要编写大量的样板文件,但幸运的是,有几个库可以减轻这种痛苦。上面已经提到了 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"
}
t98cgbkg

t98cgbkg4#

作为already stated by Vladimir Matveevfailure机箱应该是您的起点。

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

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

2ekbmq32

2ekbmq325#

看来anyhow板条箱是好的,除了不支持Box<dyn StdError>
大多数错误类型都实现了Display特征,所以请使用这个魔术。
1.自定义错误
1.从实现Display的所有对象实现From特征

pub struct MyError {
    pub code: i32,
    pub message: String,
}

impl<E: Display> From<E> for MyError {
    fn from(value: E) -> Self {
        MyError {
            code: 0,
            message: value.to_string(),
        }
    }
}

async fn hello() -> std::result::Result<(), &'static str> {
    return Err("Hello world");
}

async fn test() -> std::result::Result<(), MyError> {
    // &str
    hello().await?;
    // std::io::Error
    std::fs::read("DD")?;
    // serde_json::Error
    let data = serde_json::from_str("[1,2,3]")?;
    Ok(())
}

相关问题