如何将自定义失败与失败箱匹配

时间:2019-04-09 20:51:15

标签: error-handling rust

我正在尝试了解如何使用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`

如何制定与特定自定义错误匹配的模式?

1 个答案:

答案 0 :(得分:1)

您需要下调Error

当您通过某种实现failure::Error特征的类型创建Fail时(通过frominto来实现),您会暂时隐藏有关从编译器包装的类型。它不知道ErrorBadness -因为它也可以是任何其他Fail类型,这就是重点。您需要提醒编译器这一点,该操作称为向下转换。 failure::Error为此具有三种方法:downcastdowncast_refdowncast_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条板箱,该条板箱可以为此类枚举派生Fromsnafu看起来也很有趣,但是我还没有尝试过。在最基本的形式中,这种方法如下所示:

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),
        }
    }
}