Struggling with trait impls

In case others would like a presentation less specific to the OP: The workaround presented for a bound on trait Trait with associated type A is to define a helper trait with two type parameters, <X: Trait<A=Y>, Y>.

trait Trait { type A; }
// We wish we could...
//    impl<T: Trait<A=u8>> TargetTrait for SomethingInvolving<T> {}
//    impl<T: Trait<A=()>> TargetTrait for SomethingInvolving<T> {}
// We'll simuluate it with:
trait Helper<X: Trait<A=Y>, Y> { /* mirrors TargetTrait */ }

Because the associated type is an input in this Helper trait, it can support multiple implementations which differ by Y. Basically it works by putting the associated type in a place that coherence checks, so that you can prove to the coherence checker that everything is, in fact, non-overlapping.

Then, you can can have a (single) blanket implementation bound on the Helper trait instead of on Trait.

impl<T: Trait<A>, A> TargetTrait for SomethingInvolving<T>
where
    Self: Helper<T, A>
{ /* Just defer to `Helper::*` as the implementation */ }

Whenever you want to add more impls for more associated types of Trait, you have to impl the Helper trait instead. If you want consumers of your library to be able to do this, you'll have to expose the Helper trait too. But implementations that aren't bound on the Trait with an associated type can't rely on the Helper trait, naturally, and thus they have to implement directly:

impl TargetTrait for SomethingInvolving<TypeThatDoesNotImplTrait> {}
impl TargetTrait for SomethingElseEntirely {}

So it definitely goes beyond syntax. You also don't get any other effects of making coherence associated-type-aware, like a way to make disjoint traits. And probably other things I haven't thought of.

I could see this being a crate of some sort to support some use-cases, but not really part of the language as-is.


Incidentally, in the example as I've written it, the implementations of TargetTrait we wish we could write are non-breaking by today's definition (T is covered). But those for Helper are breaking (blanket implementations as we have exposed T in the trait). I haven't thought through if the former being allowed should be considered breaking, but it's something else to consider.


To answer this more directly, coherence explicitly ignores "outputs" such as associated types for now. And beyond that, limitations on negative reasoning to avoid changes being breaking and maintain crate compatibility.

There's a desire to improve the situation, including this specific case; however it's easy for negative reasoning to go too far and cause problems, so they're being very careful about new developments in that area. They want a comprehensive approach as well, not a use-case specific approach. There's also a lot of interactions with accepted and implemented or partially implemented RFCs, such as auto-traits and specialization.

Much more related reading here.

2 Likes