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

问题描述 投票:24回答:3

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

fn foo(...) -> Result<..., MyError> {}

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

enum MyError {
    GizmoError,
    WidgetNotFoundError(widget_name: String)
}

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

error-handling rust
3个回答
34
投票

你实现Error就像你any other trait一样;它没有什么特别之处:

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

descriptioncausesource都有默认的实现1,你的类型也必须实现DebugDisplay,因为它们是超级特征。

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")
    }
}

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

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

这是最惯用的方式吗?

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

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


我建议手工做几次,看看所有的部分是如何组合在一起的。一旦你拥有了它,你将厌倦手动完成它。然后你可以看看提供宏的板条箱来减少样板:

我首选的库是SNAFU(因为我写了它),所以这里是一个使用原始错误类型的例子:

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

注意我已经删除了每个枚举值的冗余Error后缀。通常只需调用类型Error并允许消费者为类型(mycrate::Error)添加前缀或在导入时重命名它(use mycrate::Error as FooError)。


1在RFC 2504实施之前,description是必需的方法。


2
投票

这是最惯用的方式吗?我如何实现错误特征?

这是一种常见的方式,是的。 “惯用语”取决于您希望错误输入的强度,以及您希望如何与其他内容进行互操作。

我如何实现错误特征?

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


1
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.