我正在努力了解如何使用failure箱子。它作为不同类型的标准错误的统一工作非常出色,但在创建自定义错误(Fails
)时,我不明白如何匹配自定义错误。例如:
use failure::{Fail, Error};
#[derive(Debug, Fail)]
pub enum Badness {
#[fail(display = "Ze badness")]
Level(String)
}
pub fn do_badly() -> Result<(), Error> {
Err(Badness::Level("much".to_owned()).into())
}
#[test]
pub fn get_badness() {
match do_badly() {
Err(Badness::Level(level)) => panic!("{:?} badness!", level),
_ => (),
};
}
失败了
error[E0308]: mismatched types
--> barsa-nagios-forwarder/src/main.rs:74:9
|
73 | match do_badly() {
| ---------- this match expression has type `failure::Error`
74 | Err(Badness::Level(level)) => panic!("{:?} badness!", level),
| ^^^^^^^^^^^^^^^^^^^^^ expected struct `failure::Error`, found enum `Badness`
|
= note: expected type `failure::Error`
found type `Badness`
如何制定与特定自定义错误匹配的模式?
Error
当您从某种实现failure::Error
特征的类型创建Fail
时(通过from
或into
,就像您一样),您暂时隐藏有关您从编译器中包装的类型的信息。它不知道Error
是Badness
- 因为它也可以是任何其他Fail
类型,这就是重点。你需要提醒编译器这个,这个动作叫做downcasting。 failure::Error
有三种方法:downcast
,downcast_ref
和downcast_mut
。在你向下转换之后,你可以像往常一样对结果进行模式匹配 - 但是你需要考虑到向下转换本身可能会失败的可能性(如果你试图向下转换为错误的类型)。
以下是downcast
的外观:
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
if let Ok(bad) = wrapped_error.downcast::<Badness>() {
panic!("{:?} badness!", bad);
}
}
}
(在这种情况下可以合并两个if let
s)。
如果需要测试多个错误类型,这很快就会变得非常不愉快,因为downcast
消耗它被调用的failure::Error
(因此如果第一个失败,你不能在同一个变量上尝试另一个downcast
)。遗憾的是,我无法想出一个优雅的方法来做到这一点。这是一个不应该真正使用的变体(panic!
中的map
是值得怀疑的,做其他事情会有很多尴尬,我甚至不想考虑更多的情况而不是两个):
#[derive(Debug, Fail)]
pub enum JustSoSo {
#[fail(display = "meh")]
Average,
}
pub fn get_badness() {
if let Err(wrapped_error) = do_badly() {
let e = wrapped_error.downcast::<Badness>()
.map(|bad| panic!("{:?} badness!", bad))
.or_else(|original| original.downcast::<JustSoSo>());
if let Ok(so) = e {
println!("{}", so);
}
}
}
如果你真的想从所有可能的\相关错误中产生一些相同类型的值,那么or_else
链应该可以正常工作。如果对原始错误的引用对你来说很好,也可以考虑使用非消耗方法,因为这样你就可以制作一系列if let
块,每个downcast
尝试一个。
不要将您的错误放入failure::Error
,将它们作为变体放入自定义枚举中。它是更多样板,但你可以获得无痛模式匹配,编译器也可以检查它是否合理。如果你选择这样做,我会推荐derive_more
crate,它能够为这些枚举得到From
; snafu
看起来也很有趣,但我还没有尝试过。在最基本的形式中,这种方法看起来像这样:
pub enum SomeError {
Bad(Badness),
NotTooBad(JustSoSo),
}
pub fn do_badly_alt() -> Result<(), SomeError> {
Err(SomeError::Bad(Badness::Level("much".to_owned())))
}
pub fn get_badness_alt() {
if let Err(wrapper) = do_badly_alt() {
match wrapper {
SomeError::Bad(bad) => panic!("{:?} badness!", bad),
SomeError::NotTooBad(so) => println!("{}", so),
}
}
}