How to convert between MyError type and anyhow::Error, and also handle the conversion conflict with std::error::Error?

I'm trying to implement MyError that can:

  1. anyhow::Error -> MyError.
  2. std::error::Error -> MyError.
  3. MyError -> anyhow::Error.

Here’s my current code:

#[derive(Debug)]
pub enum MyError {
    Anyhow(anyhow::Error),
    Custom,
}

impl<E> From<E> for MyError
where
    E: std::error::Error + Into<anyhow::Error>,
{
    fn from(e: E) -> Self {
        MyError::Anyhow(e.into())
    }
}

impl From<anyhow::Error> for MyError {
    fn from(e: anyhow::Error) -> Self {
        MyError::Anyhow(e)
    }
}

impl From<MyError> for anyhow::Error {
    fn from(err: MyError) -> Self {
        match err {
            MyError::Anyhow(e) => e,
            MyError::Custom => anyhow::anyhow!("CustomError"),
        }
    }
}

fn main() {
    let io_err = std::fs::read("not_exist_file").unwrap_err();

    // std::io::Error -> MyError
    let my_err_from_std_err: MyError = io_err.into();
    // MyError -> anyhow::Error
    let anyhow_err_from_my_err: anyhow::Error = my_err_from_std_err.into();
    // anyhow::Error -> MyError
    let my_err_from_anyhow: MyError = anyhow_err_from_my_err.into();
}

When I run this, I get the following error:

conflicting implementations of trait `From<anyhow::Error>` for type `MyError`
  --> src/main.rs:16:1
   |
7  | / impl<E> From<E> for MyError
8  | | where
9  | |     E: std::error::Error + Into<anyhow::Error>,
   | |_______________________________________________- first implementation here
...
16 |   impl From<anyhow::Error> for MyError {
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyError`
   |
   = note: upstream crates may add a new impl of trait `std::error::Error` for type `anyhow::Error` in future versions

I've been struggling with this issue for a whole day now and it is starting to feel like impossible to achieve.

Perhaps if I could just implement std::error::Error for MyError, could this would resolve the conflict?. If that's the case, I'd be open to a solution where MyError implements std::error::Error itself and allows for conversions to and from anyhow::Error.

I think just use Into<anyhow::Error> is enough for both cases.

EDIT:

I realize this will prevent MyError from implementing std::error::Error. if you don't need this, you can explicitly implement MyError -> anyhow::Error then.

Indeed, this is a tricky problem…

one reason why anyhow::Error itself does not implement std::error::Error is exactly conflicting trait implementations. It wants to support From<E> for anyhow::Error where E: std::error::Error and avoid conflict with the general From<T> for T implementation.

However, the coherence rules of trait implementations in Rust unfortunately don’t make this fact that anyhow::Error doesn’t (and cannot) implement std::error::Error a usable fact downstream, which is why you get the error with the remark of

= note: upstream crates may add a new impl of trait `std::error::Error` for type `anyhow::Error` in future versions

And perhaps rightfully so, because possibly in the (somewhat distant) future, Rust’s coherence rules / trait system might evolve so that anyhow::Error could become an std::error::Error-implementor, after all.


You cannot simply make your MyError implement std::error::Error for the same reason why anyhow can’t do it, because you want a generic From<E> for MyError where E: std::error::Error implementation.


I also considered unifying the first two impls into something like

impl<E> From<E> for MyError
where
    E: Into<anyhow::Error>,
{
    fn from(e: E) -> Self {
        MyError::Anyhow(e.into())
    }
}

but that conflicts with From<T> for T as soon as the impl From<MyError> for anyhow::Error is also added.


I believe that this (achieving the trait implementations you want, or a superset of them) might very well be in fact impossible.

4 Likes

Actually, the best I could come up is the following, which is a bit ugly and arbitrary-seeming… but in search of some trait bound that is fulfilled by all of impl std::error::Error + Into<anyhow::Error> and anyhow::Error itself, but not by your MyError, if you’re willing to just skip on the Display implementation, that might be a way out:

Without impl Display for MyError in place, the following can work as a superset of the former From<E> for MyError and From<anyhow::Error> for MyError:

impl<E> From<E> for MyError
where
    E: std::fmt::Display + Into<anyhow::Error>,
{
    fn from(e: E) -> Self {
        MyError::Anyhow(e.into())
    }
}

Rust Playground

This is not any more restrictive than before, because E: std::error::Error requires Display as a supertrait, anyway.

(Similarly, you could have Display but miss out on Debug for MyError; or e.g. have MyError not get a Sync or Send implementation.)

2 Likes

In practice when I'm not using concrete library error types (eg I'm handling specific error cases like "CacheExpired" or "InsufficientBuffer"), I either just use anyhow's Error as a boxed std Error (which it pretty much is), or I just need Debug or Display to print it somewhere. Not ideal, but it gets you surprisingly far!

1 Like

Thank you so much for the detailed answer! I now understand the root cause of the issue.
I really appreciate the clever trick you suggested. It works great in my project!
I will just add a comment next to E: std::fmt::Display + Into<anyhow::Error>, to explain why not use std::error::Error + Into<anyhow::Error>. That really clears things up for me.
Thanks again!

My solution to this problem is to introduce a specialized FromError trait and implement it for the target Error types, that can somehow hold other Errors:

pub trait FromError {
    fn from_error(error: impl std::error::Error) -> Self;
}

impl FromError SomeConcreteError {
    fn from_error(error: impl std::error::Error) -> Self {
        Self::new(error)
    }
}

And a WrapErr trait for the Result:

pub trait WrapErr<T> {
    fn wrap_err(self) -> T;
}

impl<T, S: std::error::Error, D: FromError> WrapErr<Result<T, D>> for Result<T, S> {
    fn wrap_err(self) -> Result<T, D> {
        self.map_err(|e| D::from_error(e))
    }
}

Usage, is something like this:

fn foobar(x: i64) -> Result<u32, SomeConcreteError> {
    x.try_into().wrap_err()
}

Ok, I have to type .wrap_err() for the foreign errors. But it's like .unwrap() and only a minor issue to me.

3 Likes

This solution comes from the author of anyhow and it works like a charm

  impl<E> From<E> for MyError
  where
-     E: std::error::Error + Into<anyhow::Error>,
+     E: Into<anyhow::Error>,
+     Result<(), E>: anyhow::Context<(), E>,
  {
      fn from(e: E) -> Self {
          MyError::Anyhow(e.into())
      }
  }
-
- impl From<anyhow::Error> for MyError {
-     fn from(e: anyhow::Error) -> Self {
-         MyError::Anyhow(e)
-     }
- }

Ah well… that’s the same idea as with the Display, just it was impossible to discover without knowledge of implementation details, because the relevant trait implementations are mostly hidden from documentation.

Surprisingly, the undocumented trait bound called StdError that appears in the docs is not just std::error::Error, but a helper trait that is implemented also for anyhow::Error. This misnomer is probably due to just lack of adjusting the name during a change in the source code a long time ago.

Anyway, this approach is better than using Display of course, because it doesn’t come with the “you type can’t be using Display anymore now” kind of limitation.