Я пытаюсь понять, как использовать ящик отказ. Он прекрасно работает как объединение разных типов стандартных ошибок, но при создании пользовательских ошибок (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`
Как я могу сформулировать шаблон, который соответствует определенной пользовательской ошибке?
К сожалению, на площадке вроде нет failure
, но это компилируется локально. (хотя и не лучший способ сделать это)
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 для обработки ошибок.
Не совсем знаком с библиотекой, но вам может понадобиться понизить содержащуюся ошибку, это, похоже, является методом для этого.