我正在编写一个可以返回几个不同错误中的几个错误的函数。
fn foo(...) -> Result<..., MyError> {}
我可能需要定义自己的错误类型来表示这样的错误。我假设这可能是错误的enum
,其中一些enum
变体附带了诊断数据:
enum MyError {
GizmoError,
WidgetNotFoundError(widget_name: String)
}
这是最惯用的方式吗?我如何实现Error
特质?
你实现Error
就像你any other trait一样;它没有什么特别之处:
pub trait Error: Debug + Display {
fn description(&self) -> &str { /* ... */ }
fn cause(&self) -> Option<&Error> { /* ... */ }
fn source(&self) -> Option<&(Error + 'static)> { /* ... */ }
}
description
,cause
和source
都有默认的实现1,你的类型也必须实现Debug
和Display
,因为它们是超级特征。
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
是必需的方法。
这是最惯用的方式吗?我如何实现错误特征?
这是一种常见的方式,是的。 “惯用语”取决于您希望错误输入的强度,以及您希望如何与其他内容进行互操作。
我如何实现错误特征?
严格来说,你不需要在这里。您可能需要与其他需要Error
的东西进行互操作,但由于您已直接将返回类型定义为此枚举,因此您的代码应该在没有它的情况下工作。
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!"
}
免责声明:我是这个箱子的作者。