I have recently been refactoring some code of mine, and ended up in a scenario where I have conflicting trait implementations. My intuition says that the implementations are falsely flagged as conflicting by the compiler, but I could be wrong.
My reasoning is that with the below implementations that the first implementation would always implement Trait2<U> for any type that implements Trait<Type=U>, which means that the type that is implemented for can only be the same associated type for Trait and generic type for Trait2. Since Trait can only be implemented once for a type, with a specific associated type, it is not possible for the same type to implement both Trait<Type=u32> and Trait<Type=f64>.
When I then say that I want to implement Trait2<f64> for a type that implements Trait<Type=u32> an implementation can not already exist, because the generic for Trait2 is not the same type as the associated type of Trait.
I have tried searching around and found something that might be related:
Is my reasoning correct? Is my issue related to the Rust issue mentioned above? As I understood it, it was said somewhere that Chalk was not the future of trait solving in Rust, but with all the issues mentioning Chalk as a reason to postpone, would it be a good time to revisit the topic?
Thank you for your help
I have simplified the code and created a playground with it:
trait Trait {
type Type;
}
trait Trait2<T> {}
impl<T, U> Trait2<U> for T where T: Trait<Type=U> {}
impl<T> Trait2<f64> for T where T: Trait<Type=u32> {}
fn main() {}
Errors:
Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `Trait2<f64>`
--> src/main.rs:9:1
|
8 | impl<T, U> Trait2<U> for T where T: Trait<Type=U> {}
| -------------------------- first implementation here
9 | impl<T> Trait2<f64> for T where T: Trait<Type=u32> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation
For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground` (bin "playground") due to previous error
I guess the compiler only looks at the Trait2<U>-vs-Trait2<f64> part. That may be because it can be hard to reason "backward" from the associated types, since they are projections (i.e., they are not necessarily 1-to-1-and-onto, as <T as Trait>::Assoc implies T -> Assoc but not the other way around).
Does that mean the where clauses are not taken into account when the compiler is looking if some implementations are conflicting?
If I specify a specific type to implement for, the trait implementation does not report a conflicting implementation, because it knows that the Trait<Type=U> has not been implemented:
trait Trait {
type Type;
}
trait Trait2<T> {}
impl<T, U> Trait2<U> for T where T: Trait<Type=U> {}
impl Trait2<f64> for u32 {}
impl Trait for u32 {
type Type=u32;
}
I don't think that's true as-is; to be clear, I'm not familiar enough with compiler internals to tell what exactly causes this error.
However, the "where clause" I think is at least somewhat of a red herring. You say…:
…and the real difference here is that you've got a specific type. And as always, it's easier to reason about specific types than it is to apply universally-quantified reasoning to generic types. I'm not saying that this definitely is the cause, but I strongly suspect it.
There's another trait solver in the works (called "next trait solver" or such in PRs) which you can consider to have taken the place of Chalk when it comes to issues saying "blocked on Chalk".
But note that it's not just "get new trait solver, turn on disjointness". A more comprehensive and intentional approach to mutually exclusive traits is desired, which will probably go hand in hand with associated type based disjointness (since the former can be implemented with the latter).