Как сопоставить пользовательские ошибки с корзиной с ошибками - PullRequest
0 голосов
/ 09 апреля 2019

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

1 голос
/ 10 апреля 2019

Вам необходимо уменьшить 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.

Anальтернатива

Не помещайте свои ошибки в 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),
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...