I've run into a problem with unconstrained type parameters where it turns out that the unconstrained type parameter is never even used. Let's say we have code like this:
trait Trait {
type Assoc: AssocTrait;
// items
}
trait SuperTrait {
fn super_fn() -> Self;
}
trait AssocTrait {
fn assoc_fn() -> Self;
}
trait Hktrait<T>: SuperTrait { /* items */ }
struct Struct<A: AssocTrait, U> {
a: A,
u: U,
}
impl<T: Trait, U: Hktrait<T>> SuperTrait for Struct<T::Assoc, U>
where
T::Assoc: AssocTrait
{
fn super_fn() -> Self {
Self {
a: T::Assoc::assoc_fn(),
u: U::super_fn(),
}
}
}
The type parameter T
is unconstrained. If the same type U
implements Hktrait<T>
for two different types T
, then the compiler would have to guess what T
is, so RFC #447 was made to avoid that. However, in this case, T
is never used. The implementation can almost be written as
impl<A: AssocTrait, U: Hktrait<T>> SuperTrait for Struct<A, U>
{
fn super_fn() -> Self {
Self {
a: A::assoc_fn(),
u: <U as SuperTrait>::super_fn(), // expanded for emphasis
}
}
}
After all, T::Assoc
is just A
, and U
has just one implementation of SuperTrait
no matter how many implementations it has of Hktrait<T>
for different T
. Unfortunately, there's still a pesky little T
in the impl
type parameters. If we could write something like
impl<A: AssocTrait, U: exists<T> Hktrait<T>> SuperTrait for Struct<A, U>
{
fn super_fn() -> Self {
Self {
a: A::assoc_fn(),
u: U::super_fn(),
}
}
}
and then enforce that the implementation cannot depend on T
, that would solve the issue and even allow us to constrain U
in Struct
without a PhantomData
:
struct Struct<A: AssocTrait, U: exists<T> Hktrait<T>> {
a: A,
u: U,
}
and then still be able to write an implementation of some other trait Hktrait2<T>
for Struct<T::Assoc, U>
.
However, the issue could just as well be solved by relaxing the bound on the implementation of SuperTrait
to U: SuperTrait
instead of U: Hktrait<T>
. In addition, it introduces new syntax, and the logic for checking whether a type parameter satisfies an existential trait bound, especially if it satisfies another existential trait bound, would need to be figured out. So is this feature interesting and useful enough for an RFC?
For context, the problem was encountered in cgmath
attempting to implement One
for Decomposed
. It turned out in that case that since Mul
needed to be implemented too and that implementation depended on T
, this wouldn't work in that case.