如何在 Rust 中定义自定义“错误”类型?

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

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

我可能需要定义自己的错误类型来表示这样的错误。我推测这可能是一个 enum的错误,一些 enum的变体有诊断数据附在它们上面:

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

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

40726 次浏览

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

这是很常见的方式,没错。“惯用”取决于您希望自己的错误具有多强类型,以及希望如何与其他事物进行互操作。

我如何实现 Error trait?

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

实现 Error就像实现 任何其他特征一样,没有什么特别之处:

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

descriptioncausesource都有默认实现 s1,而且您的类型还必须实现 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 (因为它是我编写的) ,所以这里有一个将其用于原始错误类型的示例:

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)也很常见。


RFC 2504实现之前,description是必需的方法。

箱式 自定义错误允许定义定制错误类型,其样板比上面提出的要少:

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

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