This is a minified version of a problem I ran into in an actual project. I have a trait with an associated type that is generic over a type parameter.
trait Trait {
type Value<T>;
}
struct Required;
struct Optional;
impl Trait for Required {
type Value<T> = T;
}
impl Trait for Optional {
type Value<T> = Option<T>;
}
This trait can then be used to make structs that are generic over parameter optionality:
struct TraitUser<T: Trait>(T::Value<u64>);
impl<T: Trait> TraitUser<T> {
fn new(value: T::Value<u64>) -> Self {
Self(value)
}
}
Now I would like to implement PartialEq
for TraitUser
, which requires T::Value<u64>
to be PartialEq
as well. The auto-derive macro works, although it incorrectly requires that T: PartialEq
, in addition to T::Value<u64>: PartialEq
. This can be worked around by auto-deriving PartialEq
for the ZST or by implementing PartialEq
manually.
Since the trait is used in multiple places, I would like to associate the required bounds with the trait itself. One (suboptimal but functional) solution I found to encode this, is to restrict the type parameter as well as the resulting type:
trait Trait {
type Value<T: PartialEq>: PartialEq;
}
The first bound is an obligation for the user of the trait. The second bound is a obligation for the implementer. Unfortunately, this has the side effect of restricting the use of the GAT to T
s that are PartialEq
.
Ideally, I would like to express that Self::Value<T>
must be PartialEq
for any T
that is PartialEq
-- but I guess that would require HRTBs over types, which is currently not possible. As an alternative, I would like to add bounds to the trait for a number of specific instances of T
. I believe this must be done in a where clause.
When adding the bound as a where clause on the GAT, we get overflows on the impls as well as a compile error on the use of the trait. From the second error, it seems the compiler wants us to prove at the use site that the condition holds, which is not what we want.
trait Trait {
type Value<T>
where
Self::Value<u64>: PartialEq;
}
error[E0275]: overflow evaluating the requirement
<Required as Trait>::Value<u64> == _
--> src/main.rs:16:5
|
16 | type Value<T: PartialEq> = T;
| ^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: can't compare
<T as Trait>::Value<u64>
with<T as Trait>::Value<u64>
24 | struct TraitUser<T: Trait>(T::Value);
| ^^^^^^^^^^^^^ no implementation for<T as Trait>::Value<u64> == <T as Trait>::Value<u64>
|
= help: the traitPartialEq
is not implemented for<T as Trait>::Value<u64>
note: required by a bound inTrait::Value
--> src/main.rs:4:27
Similarly, if the bound is added on the trait itself, the compiler expects us to prove that it holds everywhere the trait is used (comment the use-site where clauses to see the compiler errors).
trait Trait
where
Self::Value<u64>: PartialEq
{
type Value<T>;
}
error[E0277]: can't compare
<T as Trait>::Value<u64>
with<T as Trait>::Value<u64>
--> src/main.rs:25:21
|
25 | struct TraitUser<T: Trait>(T::Value)
| ^^^^^ no implementation for<T as Trait>::Value<u64> == <T as Trait>::Value<u64>
|
= help: the traitPartialEq
is not implemented for<T as Trait>::Value<u64>
note: required by a bound inTrait
--> src/main.rs:3:23
This confuses me, and it seems to be in contradiction to the section on supertraits in the Rust Reference, where the where bound on a trait is used to define a supertrait (i.e. to place an obligation on the implementer and provide a guarantee to the user of the trait).
My questions are as follows:
- Is this the expected compiler behavior, and if so, why?
- Is there currently a way to add the bound on (specific instantiations of) the GAT to the trait as an obligation to the implementer and a guarantee to the user of the trait?