Help in solving trait bounds with generic associated types

Consider the following struct and trait:

use std::marker::PhantomData;

struct Base<M, T>(PhantomData<M>, PhantomData<T>);

trait Super<M> {
    type T;

    fn call<M2>(&self, f: impl FnOnce(Base<M2, Self::T>))
    where
        Base<M2, Self::T>: Super<M2>;
}

impl<M> Super<M> for Base<M, ()> {
    type T = ();

    fn call<M2>(&self, f: impl FnOnce(Base<M2, Self::T>))
    where
        Base<M2, Self::T>: Super<M2>,
    {
        todo!()
    }
}

I can write a test function that takes a generic Super, and call its call method:

fn takes_super<M, S>(s: S)
where
    S: Super<M>,
{
    s.call::<u32>(|outer| {
        outer.call::<u8>(|inner| {
            //
        });
    });
}

However, it does not compile due to unsatisfied trait bound errors:

Error 1
error[E0277]: the trait bound `Base<u32, <S as Super<M>>::T>: Super<u32>` is not satisfied
  --> src/main.rs:28:7
   |
28 |     s.call::<u32>(|outer| {
   |       ^^^^ the trait `Super<u32>` is not implemented for `Base<u32, <S as Super<M>>::T>`
   |
note: required by a bound in `Super::call`
  --> src/main.rs:10:28
   |
8  |     fn call<M2>(&self, f: impl FnOnce(Base<M2, Self::T>))
   |        ---- required by a bound in this associated function
9  |     where
10 |         Base<M2, Self::T>: Super<M2>;
   |                            ^^^^^^^^^ required by this bound in `Super::call`
help: consider extending the `where` clause, but there might be an alternative better way to express this requirement
   |
26 |     S: Super<M>, Base<u32, <S as Super<M>>::T>: Super<u32>
   |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Error 2
error[E0599]: no method named `call` found for struct `Base` in the current scope
  --> src/main.rs:29:15
   |
3  | struct Base<M, T>(PhantomData<M>, PhantomData<T>);
   | ----------------- method `call` not found for this struct
...
29 |         outer.call::<u8>(|inner| {
   |         ------^^^^ method not found in `Base<u32, <S as Super<M>>::T>`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following traits define an item `call`, perhaps you need to implement one of them:
           candidate #1: `Super`
           candidate #2: `Fn`

I can fix the first error by modifying the trait bound of the test function:

fn takes_super<M, S>(s: S)
where
    S: Super<M>,
    Base<u32, <S as Super<M>>::T>: Super<u32>, // new trait bound
{
    s.call::<u32>(|outer| {
        outer.call::<u8>(|inner| {
            //
        });
    });
}

The second error, which I don't know how to fix, becomes:

Error
error[E0277]: the trait bound `Base<u8, <Base<u32, <S as Super<M>>::T> as Super<u32>>::T>: Super<u8>` is not satisfied
  --> src/main.rs:30:15
   |
30 |         outer.call::<u8>(|inner| {
   |               ^^^^ the trait `Super<u8>` is not implemented for `Base<u8, <Base<u32, <S as Super<M>>::T> as Super<u32>>::T>`
   |
   = help: the trait `Super<u8>` is implemented for `Base<u8, ()>`
   = help: for that trait implementation, expected `()`, found `<Base<u32, <S as Super<M>>::T> as Super<u32>>::T`
note: required by a bound in `Super::call`
  --> src/main.rs:10:28
   |
8  |     fn call<M2>(&self, f: impl FnOnce(Base<M2, Self::T>))
   |        ---- required by a bound in this associated function
9  |     where
10 |         Base<M2, Self::T>: Super<M2>;
   |                            ^^^^^^^^^ required by this bound in `Super::call`

Is there a way to solve these problems all together, perhaps even modifying the Super trait?
It is tedious to add the Base<u32, <S as Super<M>>::T>: Super<u32> bound.
Ideally, the test function should just take a S: Super<M>, without additional bounds.

Importantly, outer and inner should always be parametrized by the same Self::T.

Playground

@paramagnetic I am confident that this is very similar to my previous post to which you replied. Would appreciate and tip you if you could help me in solving this! :slight_smile:

fn takes_super<M, S>(s: S)
where
-     S: Super<M>,
+    S: Super<M, T=()>,

The problem is that I don't want this function to fix the T. I want the caller to be able to fix the T (provided that Base<M, T>: Super<M>):

fn main() {
    let base = Base(PhantomData::<u32>, PhantomData::<()>); // Can pass () because Base<u32, ()>: Super<u32>
    takes_super(base);

    let base = Base(PhantomData::<u32>, PhantomData::<MyStruct>); // Can pass () because Base<u32, MyStruct>: Super<u32>
    takes_super(base);
}

The bound S: Super<M, T = ()>, still applies to your last link where <Base<M, MyStruct> as Super<M>>::T == () the same as T == () in Base<M, ()>'s impl.

If your impl is

impl<M, N> Super<M> for Base<M, N> {
    type T = N;

// then 
fn takes_super<M, S>(s: S) // if S = Base<M, T>
where
    S: Super<M>, // works

Thanks for your attempt. However, I forgot to add that I want only two of my own structs to work for Base:

struct S1;
struct S2;

impl<M> Super<M> for Base<M, S1> {
    type T = S1;

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

impl<M> Super<M> for Base<M, S2> {
    type T = S2;

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

This would be the main:

let base = Base(PhantomData::<u32>, PhantomData::<S1>);
takes_super(base);

let base = Base(PhantomData::<u32>, PhantomData::<S2>);
takes_super(base);

let base = Base(PhantomData::<u32>, PhantomData::<String>);
takes_super(base); // Does not compile: OK, because Base<u32, String> does not implement Super<u32>

Is this impossible to achieve?

fn takes_super<M, S, T>(s: S)
where
    S: Super<M, T=T>, Base<u32, T>: Super<u32>

You can also remove the generic type S for your case Rust Playground

fn takes_super<M, T>(s: Base<M, T>)
where
    Base<M, T>: Super<M, T=T>, Base<u32, T>: Super<u32>
1 Like

I was able to solve the issue by introducing another associated type: Playground

use std::marker::PhantomData;

trait Super<M> {
    type T;
    type S<M2>: Super<M2>;

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

struct Base<M1, T>(PhantomData<M1>, PhantomData<T>);

struct S1;

impl<M> Super<M> for Base<M, S1> {
    type T = S1;
    type S<M2> = Base<M2, S1>;

    fn call<M2>(&self, _: impl FnOnce(Self::S<M2>))
    where
        Self::S<M2>: Super<M2>,
    {
        todo!()
    }
}

struct S2;

impl<M> Super<M> for Base<M, S2> {
    type T = S2;
    type S<M2> = Base<M2, S2>;

    fn call<M2>(&self, _: impl FnOnce(Self::S<M2>))
    where
        Self::S<M2>: Super<M2>,
    {
        todo!()
    }
}

fn takes_super<S>(s: S)
where
    S: Super<u64>,
{
    s.call::<u32>(|outer| {
        outer.call::<u8>(|inner| {
            //
        });
    });
}

fn main() {
    takes_super(Base(PhantomData, PhantomData::<S1>));
    takes_super(Base(PhantomData, PhantomData::<S2>));
    // takes_super(Base(PhantomData, PhantomData::<String>)); // Does not compile: OK!
}