我正在尝试了解如何使用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`
如何制定与特定自定义错误匹配的模式?
答案 0 :(得分:1)
Error
当您通过某种实现failure::Error
特征的类型创建Fail
时(通过from
或into
来实现),您会暂时隐藏有关从编译器包装的类型。它不知道Error
是Badness
-因为它也可以是任何其他Fail
类型,这就是重点。您需要提醒编译器这一点,该操作称为向下转换。 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
可以合并)。
如果需要测试多个错误类型,这很快就会变得非常不愉快,因为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
条板箱,该条板箱可以为此类枚举派生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),
}
}
}