-
-
Notifications
You must be signed in to change notification settings - Fork 198
Description
Currently, we have a number of crates where we have a top-level Error enum, but our functions return anyhow::Error instead of the enum. It's been pointed out that this is not idiomatic and it would be better to just return Error and use thiserror's transparent forwarding. I'm confused about how to idiomatically handle matching on forwarded error variants. Consider the following crate structure:
high-level
/ \
low-level-a low-level-b
where high-level defines Error, low-level-a defines ErrorA and low-level-b defines ErrorB. If each of the enums includes an IoError(std::io::Error) variant, how is code supposed to match on the std::io::Error? To make the question concrete, consider this code, which is what I currently imagine:
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("something bad happened: {0}")]
Bad(String),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
A(ErrorA),
#[error(transparent)]
B(ErrorB),
}
#[derive(thiserror::Error, Debug)]
enum ErrorA {
#[error(transparent)]
IoError(#[from] std::io::Error),
}
#[derive(thiserror::Error, Debug)]
enum ErrorB {
#[error(transparent)]
IoError(#[from] std::io::Error),
}
fn f() -> Result<(), Error> {
Err(Error::Bad("ouch".into()))
}
fn main() {
match f() {
Ok(()) => {
}
Err(Error::IoError(err))
| Err(Error::A(ErrorA::IoError(err)))
| Err(Error::B(ErrorB::IoError(err))) => {
// Handle the io error.
eprintln!("io error: {}", err);
}
Err(err) => {
eprintln!("An error occured: {}", err);
}
}
}This is how we currently do it:
#[derive(thiserror::Error, Debug)]
enum Error {
#[error("something bad happened: {0}")]
Bad(String),
}
#[derive(thiserror::Error, Debug)]
enum ErrorA {
}
#[derive(thiserror::Error, Debug)]
enum ErrorB {
}
fn f() -> anyhow::Result<()> {
Err(std::io::Error::new(std::io::ErrorKind::Other, "oh no!").into())
}
fn main() {
let result = f();
match result {
Err(err) => {
if let Some(err) = err.downcast_ref::<std::io::Error>() {
// Handle the io error.
eprintln!("io error: {}", err);
} else {
eprintln!("Not an io error: {}", err);
}
}
Ok(()) => {
eprintln!("Everything is fine")
}
}
}That is, we downcast to std::io::Error and it doesn't matter if the std::io::Error comes from high-level, low-level-a, low-level-b, or another crate that high-level starts using later: the user of the high-level API can reliably and compactly catch std::io::Errors.
Thanks for any insights!