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 `enum`s 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](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0626981d851e090bd9422cf1b6f18400), which is what I currently imagine: ```rust #[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](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=50e61864faf905fee75bba3634205caf) is how we currently do it: ```rust #[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::Error`s. Thanks for any insights!
This issue appears to be discussing a feature request or bug report related to the repository. Based on the content, it seems to be still under discussion. The issue was opened by nwalfield and has received 0 comments.