I’m beating my head on a complex type problem, and running into issue with how type constraints work.
I want to have something basically like this:
trait TraitWithParameter<T>{}
trait AnotherTrait{ fn implemented(&self) {println!("Implemented and called")}}
impl<T> AnotherTrait for TraitWithParameter<T>{}
But that doesn’t work because it requires me to declare it as dyn TraitWithParameter<T>{}, and that doesn’t seem to actually convince rust that types I want implement the trait unless I jump through some hoops to declare them as such.
The compiler recommends making that into:
impl<T, U> AnotherTrait for T where T: TraitWithParameter<U>{}
Except if I do that, the compiler complains that U is unconstrained. Reading up on that error, I find it does work to do:
trait TraitWithAssociatedType{
type AssociatedType;
}
impl<T, U> AnotherTrait for T where T: TraitWithAssociatedType<AssociatedType = U>{}
I don’t understand why that works and the prior version doesn’t, but more importantly I need to have a parameter on the base trait. (This is because otherwise I get conflicting implementations).
Where it gets weird is if I get desperate and try to do satisfy the constraint by doing both:
trait TraitWithBoth<T>{
type AssociatedType;
}
impl<T, U> AnotherTrait for T where T: TraitWithBoth<U, AssociatedType = U>{}
Then it’s back to complaining at me that U is unconstrained.
I’m struggling to understand this. How does appearing as both an associated type and a type parameter make this less constrained?
>Solution :
When defining:
impl<T, U> AnotherTrait for T {}
You must make it clear to the compiler what U is in some way and since you can’t implement AnotherTrait for T multiple times, U must be deducible to a singular type (based on T). If there are multiple possible values of U which can’t be specified another way, the compiler will not guess or pick one for you; it will throw an error. That is what it means when an error says a type is "unconstrained": you’ve forced the compiler to deduce a type but many are possible.
With that in mind, its not so complicated when you consider what traits can be implemented between a generic parameter, associated type, and both.
-
How many times can a
TimplementTraitWithParameter<U>? As many times as it wants because the trait is generic:struct MyStruct; impl TraitWithParameter<()> for MyStruct {} impl TraitWithParameter<u8> for MyStruct {}Thus
Ucannot be deduced and therefore would be "unconstrained" when implementingAnotherTraitforTwhereT: TraitWithParameter<U>. -
How many times can a
TimplementTraitWithAssociatedType<AssociatedType = U>? Only once since the trait is not generic:struct MyStruct; impl TraitWithAssociatedType for MyStruct { type AssociatedType = (); } impl TraitWithAssociatedType for MyStruct { type AssociatedType = u8; }error[E0119]: conflicting implementations of trait `TraitWithAssociatedType` for type `MyStruct` --> src/lib.rs:7:1 | 6 | impl TraitWithAssociatedType for MyStruct { type AssociatedType = (); } | ----------------------------------------- first implementation here 7 | impl TraitWithAssociatedType for MyStruct { type AssociatedType = u8; } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `MyStruct`Thus
Uwould be deduced when implementingAnotherTraitforTwhereT: TraitWithAssociatedType<AssociatedType = U>since there can only be one suchUand can be found byT‘s implementation ofTraitWithAssociatedType. -
Now, how many times can a
TimplementTraitWithBoth<U, AssociatedType = U>? Well again, as many times as it wants since the trait is generic:struct MyStruct; impl TraitWithBoth<()> for MyStruct { type AssociatedType = (); } impl TraitWithBoth<u8> for MyStruct { type AssociatedType = u8; }Thus
Uonce again is unconstrained and cannot be deduced based onTwhen implementingAnotherTraitforTwhereT: TraitWithBoth<U, AssociatedType = U>.
So the fact that U is assigned to an associated type doesn’t on its own mean its constrained. You must consider whether the constraint as a whole can limit deduction to a single type.