Trait generic over M with a trait method generic over Self<M2>

I have a trait Super that is generic over type parameter M and two structs S1 and S2 that are generic over M and implement Super.
All of these are part of a library. I could seal the trait so that the user of the library cannot implement the trait on its own.

The Super trait has a method call that is generic over type parameters M2 and S and takes a FnOnce closure as argument. The closure takes S as parameter. Currently, there is the trait bound S: Super<M2>.

Playground

trait Super<M> {
    fn call<M2, S>(&self, f: impl FnOnce(S))
    where
        S: Super<M2>;
}

struct S1<M1>(PhantomData<M1>);

impl<M> Super<M> for S1<M> {
    fn call<M2, S>(&self, f: impl FnOnce(S))
    where
        S: Super<M2>,
    {
        todo!()
    }
}

struct S2<M>(PhantomData<M>);

impl<M1> Super<M1> for S2<M1> {
    fn call<M2, S>(&self, f: impl FnOnce(S))
    where
        S: Super<M2>,
    {
        todo!()
    }
}

This allows me to do, for example:

let s1 = S1::<u32>(PhantomData::default());

s1.call::<u64, S1<u64>>(|inner| {}); // inner is S1<64>
s1.call::<u64, S2<u64>>(|inner| {}); // inner is S2<64>

Question

I would like to modify the Super trait so that the type of the parameter of the S of closure is restricted to be the type that is implementing the Super trait but generic over M2 instead of M.

For the sake of example (I know that this doesn't work), I want this:

trait Super<M> {
    fn call<M2, S>(&self, f: impl FnOnce(S))
    where
        S: Self<M2>;
}

After this modification, the example should behave as follows:

let s1 = S1::<u32>(PhantomData::default());

s1.call::<u64, S1<u64>>(|inner| {}); // inner is S1<64>. OK, as it can be whatever S1<M2>.
s1.call::<u64, S2<u64>>(|inner| {}); // does not compile: inner is an S2<M2> and not an S1<M2>.

Bonus

As I said at the beginning of the post, the only types implementing Super are S1 and S2.
Thus, in principle, the signature of the call method should not even require the S generic type parameter: with S1, then it is implied to be S1<M2>; with S2, then it is implied to be S2<M2>.

1 Like

You want an associated type: Playground


trait Super<M> {
    type S<M2>: Super<M2>;
    
    fn call<M2>(&self, f: impl FnOnce(Self::S<M2>))
    where
        Self::S<M2>: Super<M2>;
}

struct S1<M1>(PhantomData<M1>);

impl<M> Super<M> for S1<M> {
    type S<M2> = S1<M2>;
    
    fn call<M2>(&self, _: impl FnOnce(Self::S<M2>))
    where
        Self::S<M2>: Super<M2>,
    {
        todo!()
    }
}

struct S2<M>(PhantomData<M>);

impl<M1> Super<M1> for S2<M1> {
    type S<M2> = S2<M2>;
    
    fn call<M2>(&self, _: impl FnOnce(Self::S<M2>))
    where
        Self::S<M2>: Super<M2>,
    {
        todo!()
    }
}
6 Likes

Great, I haven't used generic associated types before thus I didn't know I could do this. Many thanks! :smiley: