Can implementations be distinguished by a generic parameter's associated type?

Consider the following (playground):

enum A {}
enum B {}
trait Identify {
    type Id;
}

trait Something<T> {}

// This implementation...
impl<T, U> Something<T> for U
where
    T: Identify<Id = A>,
{}

struct Local;

// ...conflicts with this one...
impl<T> Something<T> for Local
where
    T: Identify<Id = B>, // even though T **cannot** be the same !
{}

which fails to compile:

error[E0119]: conflicting implementations of trait `Something<_>` for type `Local`
  --> src/lib.rs:18:1
   |
10 | / impl<T, U> Something<T> for U
11 | | where
12 | |     T: Identify<Id = A>,
   | |________________________- first implementation here
...
18 | / impl<T> Something<T> for Local
19 | | where
20 | |     T: Identify<Id = B>, // even though T **cannot** be the same !
   | |________________________^ conflicting implementation for `Local`

As indicated in the code comments, it surely can never be the case that the same T is used in the two impls, since it cannot have both Id = A and Id = B? So it seems that Rust is being overly pessimistic here.

Fortunately, in my real case, I don't actually need the first Something impl (for blanket U) to be generic over its type parameter T: I can instead make explicit implementations for particular concrete type parameters—and that does indeed solve the problem... so long as Local and its Something impl are in the same crate: playground.

Unfortunately, if Local and its Something impl are downstream (my real case) it continues to conflict with upstream blanket impls even if they use concrete type parameters and even if those parameters are explicitly constrained to implementing Identify<Id = A>:

error[E0119]: conflicting implementations of trait `crate1::Something<()>` for type `Local`
 --> src/lib.rs:7:1
  |
7 | / impl<T> Something<T> for Local
8 | | where
9 | |     T: Identify<Id = B>,
  | |________________________^
  |
  = note: conflicting implementation in crate `crate1`:
          - impl<U> crate1::Something<()> for U
            where <() as crate1::Identify>::Id == A, (): crate1::Identify;

This somewhat surprised me even though I assume this is a well understood limitation, but a quick search of the issue tracker didn't throw up any obvious candidates? Grateful for any pointers to existing discussion and/or suggested workarounds.

1 Like

It seems that the new trait solver will be able to do so: Disjointness based on associated type.

this is not supported yet, but can be emulated using a helper trait trick. see this comment:

You can also try to rewrite your trait so that you can implement it for A and for B.

(It's not a "get new trait solver, code is now accepted" sort of deal.)

1 Like