rusterror-handlingtype-conversion

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

The issue seems to be a conflict between my implementation of From<anyhow::Error> and the fact that anyhow::Error already implements std::error::Error, which causes a collision.

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.

Edit:
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)
-     }
- }

Solution

  • In this case, the compiler is your friend. The answer is in the error message:

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

    The problem is, you have these two impl blocks:

    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)
        }
    }
    

    Now consider what happens when you try to convert anyhow::Error into MyError. The compiler sees the first block and tries to match: anyhow::Error implements Into<anyhow::Error> (any type T trivially implements Into<T>), but doesn't implement std::error::Error, so we should be fine here (spoiler alert: we aren't, but we'll get back to this later). It also sees the second block, where we have From<anyhow::Error>, so it obviously applies and the compiler can use it.

    However, you don't have any guarantee that the anyhow crate maintainers won't introduce an implementation for std::error::Error for anyhow::Error in the future. This sort of change only needs a minor SemVer bump, so your code could suddenly stop compiling without even changing your Cargo.toml. Rust compiler tries to prevent this and doesn't allow you to build your program, even though there's technically nothing wrong with it as of today.

    What can you do with it? Well, sadly not that much. Your options are:

    1. Removing generic From<E> implementation and implementing From for all the error types you want to support (possibly using macros) — your code stops being generic
    2. Removing the std::error::Error bound on From<E> and removing From<anyhow::Error> block — requires you to remove your impl From<MyError> for anyhow::Error block because of conflicting blanket implementation of impl<T> From<T> for T
    3. Removing the Into<anyhow::Error> bound on From<E> and using std::error::Error + Send + Sync + 'static instead while removing the From<anyhow::Error> — you won't be able to convert from anyhow::Error to MyError.

    There is a possibility this problem will be easier to tackle in the future using specialization, but it doesn't look like it will be available in stable Rust anytime soon.


    See also: How do I work around the "upstream crates may add a new impl of trait" error?