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

Posted

技术标签:

【中文标题】你如何在 Rust 中定义自定义的 `Error` 类型?【英文标题】:How do you define custom `Error` types in Rust? 【发布时间】:2020-08-11 10:47:49 【问题描述】:

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

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

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

enum MyError 
    GizmoError,
    WidgetNotFoundError(widget_name: String)

这是最惯用的方式吗?以及如何实现Error trait?

【问题讨论】:

我想你已经阅读了relevant section from the book。 :) 但鉴于存在多个错误样板箱,答案可能希望扩展的不仅仅是这个条目。 doc.rust-lang.org/rust-by-example/error/multiple_error_types/… 另见thiserror和this blog post。 【参考方案1】:

您实现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 个)主要错误类型。这些很可能是其他错误类型的枚举。这使您的 crate 的消费者无需处理类型的爆炸式增长。当然,这取决于您的 API 以及将一些错误放在一起是否有意义。

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


我建议您手动执行几次,看看所有部分如何组合在一起。一旦你有了它,你就会厌倦手动操作。然后您可以查看提供宏以减少样板的 crate:

error-chain failure quick-error Anyhow SNAFU

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

use snafu::prelude::*; // 0.7.0

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


fn foo() -> Result<(), MyError> 
    WidgetNotFoundSnafu 
        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】:

板条箱custom_error 允许定义自定义错误类型,其样板代码比上面建议的要少:

custom_error!MyError
     iosource: io::Error             = "input/output error",
     WidgetNotFoundErrorname: String = "could not find widget 'name'",
     GizmoError                        = "A gizmo error occurred!"

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

【讨论】:

【参考方案3】:

这是最惯用的方式吗?以及如何实现 Error trait?

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

我该如何实现 Error trait?

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

【讨论】:

以上是关于你如何在 Rust 中定义自定义的 `Error` 类型?的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个键值条目的 JSON 对象反序列化为 Rust 中的自定义结构

通过 Rust 宏自定义文字?

如何使用 $util.error 在 AppSync 中发送自定义错误

「Rust笔记」Rust之自定义宏写法

如何在猫鼬中返回自定义错误?

Rust所有权进阶部分