Compiler complain about conflicting implementations but code is not

use actix::{Actor, Context, StreamHandler};

#[actix::main]
async fn main() {

    P::<S1,S2> {
        t1: S1,
        t2: S2
    };
}

pub struct P<TT1,TT2> where TT1: T1+ Unpin + 'static , TT2: T2+ Unpin + 'static {
    t1: TT1 ,
    t2: TT2
}

impl<TT1,TT2> Actor for P<TT1,TT2> where TT1: T1+ Unpin+ 'static , TT2: T2+ Unpin+ 'static  {
    type Context = Context<Self>;
}

impl<TT1,TT2> StreamHandler<TT1::Item> for P<TT1,TT2> where TT1: T1+ Unpin+ 'static , TT2: T2+ Unpin+ 'static  {
    fn handle(&mut self, item: TT1::Item, ctx: &mut Self::Context) {
        todo!()
    }
}

impl<TT1,TT2> StreamHandler<TT2::Item> for P<TT1,TT2> where TT1: T1+ Unpin+ 'static , TT2: T2+ Unpin+ 'static  {
    fn handle(&mut self, item: TT2::Item, ctx: &mut Self::Context) {
        todo!()
    }
}


pub trait T1 {
    type Item;
}

pub trait T2 {
    type Item;
}

struct A1;

struct A2;

struct S1;

struct S2;

impl T1 for S1 {
    type Item = A1;
}

impl T2 for S2 {
    type Item = A2;
}

rustc 1.77.2 returns

error[E0119]: conflicting implementations of trait `StreamHandler<_>` for type `P<_, _>`
  --> examples/stream.rs:27:1
   |
21 | impl<TT1,TT2> StreamHandler<TT1::Item> for P<TT1,TT2> where TT1: T1+ Unpin+ 'static , TT2: T2+ Unpin+ 'static  {
   | ------------------------------------------------------------------------------------------------------------- first implementation here
...
27 | impl<TT1,TT2> StreamHandler<TT2::Item> for P<TT1,TT2> where TT1: T1+ Unpin+ 'static , TT2: T2+ Unpin+ 'static  {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `P<_, _>`

I know i can‘t guarantee type TT1::Item is different from TT2::Item , so compiler complained . But in real code , we can see A1 is different from A2 , and also generics ultimately need to determine the type.  So why can't we analyze from the final code to avoid this ?

Questions about current behavior are better suited to URLO.

Anyway, coherence checks don't consider the current set of implementations to be absolute, they also guard against possible future implementations.[1]

On top of that, though it doesn't apply to your example, coherence doesn't consider even disjointness based on associated types being unique per implementor, even though it logically could without forward-compatibility perils. If you follow the rabbit hole via that link far enough, you'll find various conversations about the perils of negative reasoning in the coherence system.

(Moreover, where clauses generally aren't considered from a coherence POV.)


  1. If you have a fixed set of implementations of T1 and T2, you can have a fixed set of implementations to replace your example blanket implementations. ↩︎