How does a conflicting implementation of `From` occur when using a generic type?

I am trying to implement an error enumeration that may contain an error related to one of our features, for example:

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

I also tried to implement the From trait so that I could create MyError from an instance of Storage::Error :

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

( playground )

However, this does not 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 do not understand why the compiler believes that this is already implemented. The error message tells me that there is already an implementation From<MyError<_>> (which is), but I am not trying to implement it here - I am trying to implement From<S::Error> and MyError not the same type as S::Error from what I see.

Am I missing something here because of generics?

+7
source share
2 answers

The problem is that someone can implement Storage , so From impl you wrote matches with impl in the standard impl<T> From<T> for T library (i.e. everything can be converted to itself).

In particular,

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

(The setting here means that it does not actually compile - MyError<Tricky> is infinitely large - but this error is not related to discussions about impl s / coherence / overlap and really small changes to MyError can compile it without changing the fundamental problem, for example, add Box as StorageProblem(Box<S::Error>), )

If we replace Tricky instead of S with your impl, we get:

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

This impl exactly matches self-conversion with T == MyError<Tricky> , and therefore, the compiler does not know which one to choose. Instead of random / random selection, the Rust compiler avoids such situations, so the source code must be rejected due to this risk.

This limitation of connectivity can definitely be annoying and is one of the reasons that specialization is a highly anticipated feature: it essentially allows the compiler to manually instruct how to handle the overlap ... at least one of the extensions to the current restricted form allows this.

+9
source

A workaround for the consistency problem is to use Result::map_err to do the conversion yourself. Then you can use end Result with try! or ? :

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

This solution is also useful when there are error options that have the same basic Error , for example, if you want to separate the file open and file read errors, both of which are io::Error .

+3
source

All Articles