Understanding conflicting blanket implementation when a new candidate is defined

Hello! Sorry if this is a common question; it's a bit hard to search...
I am struggling to find a properly substantiated explanation (not just a guess) as to why the following happens:

Consider this:

struct Wrapper<T>(T);
trait E {}

impl<A, B> From<A> for Wrapper<B>
where
    A: Into<B> + E,
{
    fn from(value: A) -> Self {
        Self(value.into())
    }
}

struct X {}
impl E for Wrapper<X> {}

struct Y {}
impl E for Wrapper<Y> {}

fn main() {}

This does not compile:

error[E0119]: conflicting implementations of trait `From<Wrapper<_>>` for type `Wrapper<_>`
 --> src/main.rs:4:1
  |
4 | / impl<A, B> From<A> for Wrapper<B>
5 | | where
6 | |     A: Into<B> + E,
  | |___________________^
  |
  = note: conflicting implementation in crate `core`:
          - impl<T> From<T> for T;

I assume it's considering the possibility where, in the context of the blanket implementation, for some type U, A = Wrapper<U> and B = U; thus the implementation looks like impl<U> From<Wrapper<U>> for Wrapper<U>, which conflicts with the core implementation of impl<T> From<T> for T (T = Wrapper<U>).

But then, the actual question: why does it compile if one removes the line impl E for Wrapper<Y> {}, so there is only one implementation of E for a concrete type (X), or both of them?

  1. // impl E for Wrapper<X> {}
    // impl E for Wrapper<Y> {}
    
    Compiles;
  2. impl E for Wrapper<X> {}
    // impl E for Wrapper<Y> {}
    
    // or vice-versa
    
    Compiles;
  3. impl E for Wrapper<X> {}
    impl E for Wrapper<Y> {}
    // or more
    
    Does not compile.

How are cases 2 and 3, in particular, different? Does having two distinct types implementing that same trait somehow enable an overlap that does not occur with only one and I'm not seeing? Is the compiler analyzing generally different things in one case vs the other?

1 Like

I have received a response from someone that's friends with someone that works on the compiler. I did not understand the entire mechanism, but my conclusion is that, when there is only one impl E for Wrapper<_> {}, the compiler is somehow able to know that the blanket's where clause A: Into<B> + E applies to only one type, so it's able to refute the possibility of there being an overlap. The moment there are two or more, however, it still would be able to, but it doesn't. The compiler only "knows" when there is one impl and "doesn't know" when there are more. Source is that anonymous Rust developer, which is something, I guess, though I summarized the response here based on my own understanding.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.