Как сопоставить пользовательские сбои с ящиком сбоев

Я пытаюсь понять, как использовать ящик отказ. Он прекрасно работает как объединение разных типов стандартных ошибок, но при создании пользовательских ошибок (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`

Как я могу сформулировать шаблон, который соответствует определенной пользовательской ошибке?

Не совсем знаком с библиотекой, но вам может понадобиться понизить содержащуюся ошибку, это, похоже, является методом для этого.

Michail 09.04.2019 23:03

К сожалению, на площадке вроде нет failure, но это компилируется локально. (хотя и не лучший способ сделать это)

Michail 09.04.2019 23:23
Знайте свои исключения!
Знайте свои исключения!
В Java исключение - это событие, возникающее во время выполнения программы, которое нарушает нормальный ход выполнения инструкций программы. Когда...
0
2
346
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 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),
        }
    }
}
Не помещайте свои ошибки в failure::Error, поместите их в собственное перечисление как варианты. — на самом деле это важный пункт моего Ящик СНАФУ, который улучшает стандартные перечисления Rust для обработки ошибок.
Shepmaster 10.04.2019 16:40

Другие вопросы по теме