37

I'm trying to implement an error enum which can contain an error associated with one of our traits like this:

trait Storage {
    type Error;
}

enum MyError<S: Storage> {
    StorageProblem(S::Error),
}

I have also tried to implement the From trait to allow construction of MyError from an instance of a Storage::Error:

impl<S: Storage> From<S::Error> for MyError<S> {
    fn from(error: S::Error) -> MyError<S> {
        MyError::StorageProblem(error)
    }
}

(playground)

However this fails to compile:

error[E0119]: conflicting implementations of trait `std::convert::From<MyError<_>>` for type `MyError<_>`:
 --> src/lib.rs:9:1
  |
9 | impl<S: Storage> From<S::Error> for MyError<S> {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> std::convert::From<T> for T;

I don't understand why the compiler reckons this has already been implemented. The error message is telling me that there's already an implementation of From<MyError<_>> (which there is), but I'm not trying to implement that here - I'm trying to implement From<S::Error> and MyError is not the same type as S::Error from what I can see.

Am I missing something fundamental to generics here?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Fraser
  • 74,704
  • 20
  • 238
  • 215

2 Answers2

21

The problem here is someone may implement Storage so that the From impl you have written overlaps with the impl in the standard library of impl<T> From<T> for T (that is, anything can be converted to itself).

Specifically,

struct Tricky;

impl Storage for Tricky {
    type Error = MyError<Tricky>;
}

(The set-up here means this doesn't actually compile—MyError<Tricky> is infinitely large—but that error is unrelated to the reasoning about impls/coherence/overlap, and indeed small changes to MyError can make it compile without changing the fundamental problem, e.g. adding a Box like StorageProblem(Box<S::Error>),.)

If we substitute Tricky in place of S in your impl, we get:

impl From<MyError<Tricky>> for MyError<Tricky> {
    ...
}

This impl exactly matches the self-conversion one with T == MyError<Tricky>, and hence the compiler wouldn't know which one to choose. Instead of making an arbitrary/random choice, the Rust compiler avoids situations like this, and thus the original code must be rejected due to this risk.

This coherence restriction can definitely be annoying, and is one of reasons that specialisation is a much-anticipated feature: essentially allows manually instructing the compiler how to handle overlap... at least, one of the extensions to the current restricted form allows that.

huon
  • 94,605
  • 21
  • 231
  • 225
  • Has the extension been implemented in nightly now? – WiSaGaN May 20 '16 at 13:08
  • No, and in fact the restricted form is still available only in nightly. I believe the intention is to let that bake for a bit/iron out the bugs before diving into the more flexible/powerful versions. – huon May 20 '16 at 13:10
  • 1
    A clear answer: thanks. I guess the error message could be a bit clearer if it said something like there *could be* conflicting implementations rather than implying there already are. – Fraser May 20 '16 at 13:11
  • @Fraser, yeah, definitely, I guess it may be covered by https://github.com/rust-lang/rust/issues/23980 (I'll write a comment summarising this particular piece of confusion there). – huon May 20 '16 at 13:14
  • 1
    I think it would work if you could specify that U != T. Maybe it is a naive reasonment, but I don't see why it's not a feature from the language – rambi Jul 07 '21 at 15:18
13

A workaround for the coherence issue is to use some other method or trait.

In the specific posted example involving Results, you can use Result::map_err to perform the conversion yourself. You can then use the transformed Result with ?:

fn example<S: Storage>(s: S) -> Result<i32, MyError<S>> {
    s.do_a_thing().map_err(MyError::StorageProblem)?;
    Ok(42)
}

This solution is also valuable when there are error variants that have the same underlying Error, such as if you want to separate "file opening" and "file reading" errors, both of which are io::Error.

In other cases, you might need to create a brand new method on your type or an alternate trait:

struct Wrapper<T>(T);

// Instead of this
//
// impl<T, U> From<Wrapper<T>> for Wrapper<U>
// where
//     T: Into<U>,
// {
//     fn from(other: Wrapper<T>) -> Self {
//         Wrapper(other.0.into())
//     }
// }

// Use an inherent method

impl<T> Wrapper<T> {
    fn from_another<U>(other: Wrapper<U>) -> Self
    where
        U: Into<T>,
    {
        Wrapper(other.0.into())
    }
}

// Or create your own trait

trait MyFrom<T> {
    fn my_from(other: T) -> Self;
}

impl<T, U> MyFrom<Wrapper<T>> for Wrapper<U>
where
    T: Into<U>,
{
    fn my_from(other: Wrapper<T>) -> Self {
        Wrapper(other.0.into())
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366