Unexpected conflicting trait implementations

I tried to use an associated type to choose between two trait implementations, but the compiler is complaining that the implementations conflict. I expected this to work because any given type can have at most one Discriminator implementation and therefore cannot be covered by more than one of the impl TwoImpls block: if Arg is A it cannot also be B, and vice versa.

I can think of a few reasons that this might not work:

  • There a flaw in my logic that means these actually overlap.
  • They don't overlap, but this is forbidden for ergonomic reasons.
  • This should work in chalk, but is not currently expected to work.
  • This should work now, and is a bug that needs to be reported.

I know that I can make this work by lowering the implementations onto the individual Discriminator::Arg types, but is there a way to make something like this approach work?

struct A;
struct B;

trait Discriminator { type Arg; }

trait TwoImpls {
    fn print(&self);
}

impl<T: Discriminator<Arg=A>> TwoImpls for T {
    fn print(&self) { println!("A"); }
}

impl<T: Discriminator<Arg=B>> TwoImpls for T {
    fn print(&self) { println!("B"); }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `TwoImpls`:
  --> src/lib.rs:14:1
   |
10 | impl<T: Discriminator<Arg=A>> TwoImpls for T {
   | -------------------------------------------- first implementation here
...
14 | impl<T: Discriminator<Arg=B>> TwoImpls for T {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

error: aborting due to previous error

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

1 Like

I haven't tried this, but I'd expect making the associated type a type parameter instead could possibly work.

You mean defining it as trait Discriminator<Arg> {} instead? The two impls would actually be in conflict in that case, because a single type could implement both Discriminator<A> and Discriminator<B>: there's no way for the compiler to resolve the ambiguity.

It looks like this is a very long-standing issue, and a fix should become possible after the new Chalk-based trait system is integrated into the compiler.

3 Likes

For completeness, here’s the working version with lowererd impls:

struct A;
struct B;

trait Discriminator { type Arg; }

trait TwoImpls {
    fn print(&self);
}

trait TwoImplsLower<T:TwoImpls> {
    fn print(x: &T);
}

impl<T:Discriminator> TwoImpls for T
where T::Arg: TwoImplsLower<T> {
    fn print(&self) { T::Arg::print(self) }
}

impl<T: Discriminator<Arg=A>> TwoImplsLower<T> for A {
    fn print(_: &T) { println!("A"); }
}

impl<T: Discriminator<Arg=B>> TwoImplsLower<T> for B {
    fn print(_: &T) { println!("B"); }
}

(Playground)

1 Like