如何在Rust中定义自定义`Error`类型?

Jo *_*iss 33 error-handling rust

我正在编写一个可以返回几个不同错误中的几个错误的函数.

fn foo(...) -> Result<..., MyError> {}
Run Code Online (Sandbox Code Playgroud)

我可能需要定义自己的错误类型来表示这样的错误.我假设这可能是一个enum错误,一些enum变种附带了诊断数据:

enum MyError {
    GizmoError,
    WidgetNotFoundError(widget_name: String)
}
Run Code Online (Sandbox Code Playgroud)

这是最惯用的方式吗?我该如何实现这个Error特性?

She*_*ter 42

你的实现Error任何其他特征完全一样; 它没有什么特别之处:

pub trait Error: Debug + Display {
    fn description(&self) -> &str { /* ... */ }
    fn cause(&self) -> Option<&Error> { /* ... */ }
    fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

description,causesource都具有默认实现1,和你的类型也必须实现DebugDisplay,因为他们是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")
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,Thing包含的内容以及方法的实现在很大程度上取决于您希望拥有的错误类型.也许你想在那里包含一个文件名,或者某种整数.也许你想要一个enum而不是一个struct来代表多种类型的错误.

如果最终包装现有错误,那么我建议实现From在这些错误和错误之间进行转换.这使您可以使用try!?拥有一个非常符合人体工程学的解决方案.

这是最惯用的方式吗?

在惯用语中,我会说库会有一小部分(可能是1-3个)暴露的主要错误类型.这些可能是其他错误类型的枚举.这使您的箱子的消费者不会处理类型的爆炸.当然,这取决于您的API以及将某些错误归结为一起是否有意义.

另外需要注意的是,当您选择在错误中嵌入数据时,可能会产生广泛的后果.例如,标准库在文件相关错误中不包含文件名.这样做会增加每个文件错误的开销.方法的调用者通常具有相关的上下文,并且可以决定是否需要将该上下文添加到错误中.


我建议手工做几次,看看所有的部分是如何组合在一起的.一旦你拥有了它,你将厌倦手动完成它.然后,您可以检查快速错误,错误链故障等包含宏以减少样板的包.

我首选的库是快速错误,所以这里是一个使用原始错误类型的示例:

// This example uses the simpler syntax supported in Rust 1.34
use snafu::Snafu; // 0.2.0

#[derive(Debug, Snafu)]
enum MyError {
    #[snafu(display("Refrob the Gizmo"))]
    Gizmo,
    #[snafu(display("The widget '{}' could not be found", widget_name))]
    WidgetNotFound { widget_name: String }
}

fn foo() -> Result<(), MyError> {
    WidgetNotFound { widget_name: "Quux" }.fail()
}

fn main() {
    if let Err(e) = foo() {
        println!("{}", e);
        // The widget 'Quux' could not be found
    }
}
Run Code Online (Sandbox Code Playgroud)

注意我已经删除了Error每个枚举值的冗余后缀.通常只调用类型Error并允许使用者为type(mycrate::Error)添加前缀或在import(use mycrate::Error as FooError)上重命名它.


1在实施RFC 2504之前,这description是一种必需的方法.

  • 感谢您提供另一个详细,有用的答案-特别是关于从滚动自己开始,然后转到一些板条箱以减少样板的建议。非常感激! (2认同)

lov*_*soa 11

crate custom_error允许使用比上面建议的更少的样板定义自定义错误类型:

custom_error!{MyError
     Io{source: io::Error}             = "input/output error",
     WidgetNotFoundError{name: String} = "could not find widget '{name}'",
     GizmoError                        = "A gizmo error occurred!"
}
Run Code Online (Sandbox Code Playgroud)

免责声明:我是这个板条箱的作者。


Ste*_*nik 5

这是最惯用的方法吗?我该如何实现 Error 特征?

这是一种常见的方式,是的。“惯用”取决于您希望错误的强类型化程度,以及您希望它如何与其他事物互操作。

我该如何实现 Error 特征?

严格来说,你不需要在这里。您可能需要与其他需要的东西进行互操作Error,但由于您已将返回类型直接定义为此枚举,因此您的代码应该可以在没有它的情况下工作。