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.