你如何在 Rust 中定义自定义的 `Error` 类型? [英] How do you define custom `Error` types in Rust?

查看:22
本文介绍了你如何在 Rust 中定义自定义的 `Error` 类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing a function that could return several one of several different errors.

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

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

I'll probably need to define my own error type to represent such errors. I'm presuming it would be an enum of possible errors, with some of the enum variants having diagnostic data attached to them:

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

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

Is that the most idiomatic way to go about it? And how do I implement the Error trait?

推荐答案

你实现了 错误 完全像你会任何其他特征;没什么特别的:

You implement Error exactly like you would any other trait; there's nothing extremely special about it:

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

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

description, cause, and source all have default implementations1, and your type must also implement Debug and Display, as they are 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")
    }
}

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

Of course, what Thing contains, and thus the implementations of the methods, is highly dependent on what kind of errors you wish to have. Perhaps you want to include a filename in there, or maybe an integer of some kind. Perhaps you want to have an enum instead of a struct to represent multiple types of errors.

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

If you end up wrapping existing errors, then I'd recommend implementing From to convert between those errors and your error. That allows you to use try! and ? and have a pretty ergonomic solution.

这是最惯用的方式吗?

习惯上,我会说一个库将有少量(可能是 1-3 个)暴露的主要错误类型.这些很可能是其他错误类型的枚举.这使您的 crate 的消费者不必处理类型的爆炸式增长.当然,这取决于您的 API 以及将一些错误归为一类是否有意义.

Idiomatically, I'd say that a library will have a small (maybe 1-3) number of primary error types that are exposed. These are likely to be enumerations of other error types. This allows consumers of your crate to not deal with an explosion of types. Of course, this depends on your API and whether it makes sense to lump some errors together or not.

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

Another thing to note is that when you choose to embed data in the error, that can have wide-reaching consequences. For example, the standard library doesn't include a filename in file-related errors. Doing so would add overhead to every file error. The caller of the method usually has the relevant context and can decide if that context needs to be added to the error or not.

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

I'd recommend doing this by hand a few times to see how all the pieces go together. Once you have that, you will grow tired of doing it manually. Then you can check out crates which provide macros to reduce the boilerplate:

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

My preferred library is SNAFU (because I wrote it), so here's an example of using that with your original error type:

// 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::错误为 FooError).

Note I've removed the redundant Error suffix on each enum value. It's also common to just call the type Error and allow the consumer to prefix the type (mycrate::Error) or rename it on import (use mycrate::Error as FooError).

1RFC 2504 已实现,description 是必需的方法.

1 Before RFC 2504 was implemented, description was a required method.

这篇关于你如何在 Rust 中定义自定义的 `Error` 类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆