Cannot create catch-all error variant

Still struggling with my errors … I have an error enum where I’d like to use one variant for a ‘catch-all’ From implementation that should make the ? operator work with all errors.

Is this possible at all? This doesn’t compile:

use std::error::Error;

#[derive(Debug)]
enum MyError {
    A, B, C,
    Catchall(Box<dyn Error + Send + Sync>),
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "my error")
    }
}

impl Error for MyError {}

impl<E> From<E> for MyError
where
    E: Error + Send + Sync + 'static,
{
    fn from(error: E) -> Self {
        Self::Catchall(error.into())
    }
}
error[E0119]: conflicting implementations of trait `std::convert::From<MyError>` for type `MyError`:
  --> src/lib.rs:17:1
   |
17 | / impl<E> From<E> for MyError
18 | | where
19 | |     E: Error + Send + Sync + 'static,
20 | | {
...  |
23 | |     }
24 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`:
           - impl<T> std::convert::From<T> for T;

Why is this? If this catch-all really can’t be made to work, what would be the alternatives?

That's because MyError implements Error itself, so in impl From<E: Error> for MyError, it's possible to substitute E = MyError, which in turn clashes with the impl From<T> for T blanket impl defined in the standard library.

A workaround could be to define a new trait and implement it for all other error types you want to work with, except MyError.

You might also want to look into Anyhow:

https://docs.rs/anyhow/1.0.20/anyhow/index.html

It creates a catch-call error that can print error chains and make it easy to add context.

But how does anyhow do it, they seem to be able to do exactly what I tried!

https://docs.rs/anyhow/1.0.20/anyhow/struct.Error.html#impl-From<E>

To give a bit of context: I’m doing a callback-based API where functions may return Result<T, MyError>.

Those functions will ultimately be written by someone else. The idea was to have a ‘catchall’ implementation such that the ? would work with whatever error users have to deal with in their function bodies.

fn callback() -> Result<(), MyError> {
    let _ = std::fs::File::open("blablablabla")?;

    Ok(())
}
error[E0277]: `?` couldn't convert the error to `MyError`
  --> src/lib.rs:27:48
   |
27 |     let _ = std::fs::File::open("blablablabla")?;
   |                                                ^ the trait `std::convert::From<std::io::Error>` is not implemented for `MyError`
   |
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = note: required by `std::convert::From::from`

The anyhow error doesn't implement error itself, so it doesn't clash.

Typically, that is considered bad practice and libraries don't do that. Instead, the user of the library has to ensure they provide conversions from their own error types.

You mean my catchall approach is bad practice, right? And the idea is that people should use map_err, is that it? I'll try it in a minute.

All right I think I’m getting there. Impl is verbose and boilerplatey but the end result looks good.

#[derive(Debug)]
enum MyError {
    A, B, C,
    Catchall(Box<dyn Error + Send + Sync>),
}

// Display and Error impls as before

impl From<Box<dyn Error + Send + Sync + 'static>> for MyError {
    fn from(error: Box<dyn Error + Send + Sync + 'static>) -> Self {
        Self::Catchall(error)
    }
}

impl MyError {
    fn boxed<E>(e: E) -> Box<dyn Error + Send + Sync + 'static>
    where
        E: Error + Send + Sync + 'static,
    {
        Box::new(e)
    }
}
fn callback() -> Result<(), MyError> {
    let _ = std::fs::File::open("blablablabla").map_err(MyError::boxed)?;

    Ok(())
}

What do you think, is this a good solution?

Either map_err for ad-hoc cases, but what I meant by "providing the conversion" is that they are expected to implement From on their own error types, in general.

The idea is that using a dyn Error in libraries is bad practice because your users lose the ability to match in your errors.

1 Like

Yes, Anyhow is great for providing a catch all for applications or CLI's that need to be able to catch every different kind of error and handle it appropriately without panicking.

For libraries it is best practice to implement From for all of the possible errors that your library will run into from the libraries that it uses. For example, if your library does IO and and you need to handle std::io::Error in your code. You want to impl From<std::io::Error> for MyError. Once you do that, in your library code you can do something like this:

fn do_something() -> Result<(), MyError> {
    // Here we are able to use the `?` operator to propagate an std::io::Error
    // because we have implemented `From<std::io::Error>` for `MyError`.
    let value = function_that_may_have_io_error()?;
}

That way any user of your library will have a concrete error type returned by your functions that they an match on and make sure they handle appropriately.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.