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