This is a reply to Family pattern and “restricting” associated types asked by sampsyo - the original question has been closed but I wanted to share my solution, hence opening a new topic.
For a recap of the problem, either read the original topic or read this code.
The idealized solution, as proposed by quinedot relies on at least two (and possibly three) things:
// 1. GAT equality vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
trait MeowFamily: Family<for<T> Assoc<T> = Self::MeowAssoc<T>> {
type MeowAssoc<T>: Trait<T> + Meow;
}
impl<F> MeowFamily for F
where
F: Family,
// 2. non-lifetime higher-ranked trait bounds (HRTBs)
for<T> F::Assoc<T>: Meow,
// ^^^^^^^^^^^^^^^^^^^^^^^^^
{
type MeowAssoc<T> = F::Assoc<T>;
}
and the third being constrained non-lifetime binders, so that it would be possible to write for<T where T: Sized> Blah<T>
as without this, all trait/struct generics must have no bounds at all, not even the implicit Sized
, otherwise the current incomplete non-lifetime binders implementation will complain about unsatisfied trait bounds.
In quinedot's final version, the workaround for 1 was to explicitly declare partial equality on certain types:
trait MeowFamily
where
Self: Family<Assoc<u32> = Self::MeowAssoc<u32>>,
Self: Family<Assoc<f64> = Self::MeowAssoc<f64>>,
and the workaround for 3 was to add ?Sized
everywhere, and use Box
-ed values to hold generics (not ideal.) The full solution is here.
My attempt at answering largely kept the ?Sized
everywhere adjustments, and the resulting Box
-ing. The part of my solution that was different, is a different workaround for 1: instead of explicitly declaring partial equality on certain types, I used a type-witness helper trait instead.
pub trait TyEq {
type Rhs: TyEq<Rhs = Self> + ?Sized;
fn eq_refl(self) -> Self::Rhs where Self: Sized;
fn eq_refl_inv(rhs: Self::Rhs) -> Self where Self::Rhs: Sized;
fn ref_eq_refl(&self) -> &Self::Rhs;
fn ref_eq_refl_inv(rhs: &Self::Rhs) -> &Self;
fn mut_eq_refl(&mut self) -> &mut Self::Rhs;
fn mut_eq_refl_inv(rhs: &mut Self::Rhs) -> &mut Self;
}
impl<T: ?Sized> TyEq for T {
type Rhs = T;
fn eq_refl(self) -> T where T: Sized { self }
fn eq_refl_inv(rhs: T) -> Self where T: Sized { rhs }
fn ref_eq_refl(&self) -> &T { self }
fn ref_eq_refl_inv(rhs: &T) -> &Self { rhs }
fn mut_eq_refl(&mut self) -> &mut T { self }
fn mut_eq_refl_inv(rhs: &mut T) -> &mut Self { rhs }
}
Then the MeowFamily
trait can be defied in terms of TyEq
, like so
trait MeowFamily: Family {
type MeowAssoc<T: ?Sized>: TyEq<Rhs = Self::Assoc<T>> + Trait<T> + Meow + ?Sized;
}
impl<F: Family + ?Sized> MeowFamily for F
where for<T> Self::Assoc<T>: Meow
{
type MeowAssoc<T: ?Sized> = Self::Assoc<T>;
}
Finally, we can use the type-witness methods in order to write the challenge impl
block
impl<M: MeowFamily + ?Sized> Wrapper<M>
where M::Assoc<u32>: Sized
{
fn speak(&self) {
M::MeowAssoc::ref_eq_refl_inv(&self.int).meow();
M::MeowAssoc::ref_eq_refl_inv(&self.float).meow();
}
}
And we get the following successful demonstration:
fn main() {
let one = Wrapper::<OneFamily>::new();
let two = Wrapper::<TwoFamily>::new();
let three = Wrapper::<ThreeFamily>::new();
// one.speak(); // doesn't (and shouldn't) work here, but these do:
two.speak();
three.speak();
}
The full version of my final answer is here.
Now the question/challenge I have to yet others, who are struggling with the workarounds to 3: have you come up with any good approaches to work around the lack of constrained non-lifetime binders in a reasonable way?