[Solved] Cannot apply HRTB to associated item on trait bound

I have the following case where there's a non-object-safe trait SystemData coming from an external crate, and I would like to create a trait object that can call one of it's functions. I've created an intermediate trait that is object safe and implemented it for PhantomData<T> where T implements SystemData. There's a second trait PrefabData that has an associated type bound on SystemData. I'd like to be able to take a type that implements PrefabData and then create a trait object out of its associated type.

The tricky part is that both SystemData and PrefabData have a generic lifetime parameter. In most cases I'm able to use HRTB when writing trait implementations or functions to get this working (see example below). However, I've found that the compiler can't seem to follow an HRBT through an associated type.

Is there a way to write the trait bounds for T in the register_component<T> function such that the below code will compile? And if not, why? It seems to me like the below code should be sufficient, though my understanding of HRTBs is pretty weak (this is the first time I've ever used them).

For slightly more context, here's a more complete example that includes some extra traits that do compile. Note that all the ones that do compile apply the bounds to the trait directly and don't need the bounds to be threaded through associated types.

I appreciate any help/guidance anyone can provide!


use std::marker::PhantomData;

// >>> BELOW CODE IS FROM AN EXTERNAL CRATE AND CANNOT BE CHANGED <<< //

pub struct Resources;

pub trait SystemData<'a> {
    fn setup(res: &Resources);
}

pub trait PrefabData<'a> {
    type SystemData: SystemData<'a>;
}

// >>> BELOW CODE IS TRAIT DEFINITIONS AND LIKELY SHOULD NOT BE CHANGED <<< //

pub trait SystemDataSetup {
    fn setup(&self, res: &mut Resources);
}

impl<T> SystemDataSetup for PhantomData<T>
where
    for<'a> T: SystemData<'a>,
{
    fn setup(&self, res: &mut Resources) {
        T::setup(res);
    }
}

// >>> BELOW CODE CAN BE CHANGED <<< //

pub fn register_component<T>()
where
    T: 'static,
    for<'a> T:
        PrefabData<'a>,
{
    let _ = Box::new(PhantomData::<T::SystemData>) as Box<dyn SystemDataSetup>;
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `for<'a> <T as PrefabData<'_>>::SystemData: SystemData<'a>` is not satisfied
  --> src/main.rs:39:13
   |
39 |     let _ = Box::new(PhantomData::<T::SystemData>) as Box<dyn SystemDataSetup>;
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `for<'a> SystemData<'a>` is not implemented for `<T as PrefabData<'_>>::SystemData`
   |
   = help: consider adding a `where for<'a> <T as PrefabData<'_>>::SystemData: SystemData<'a>` bound
   = note: required because of the requirements on the impl of `SystemDataSetup` for `std::marker::PhantomData<<T as PrefabData<'_>>::SystemData>`
   = note: required for the cast to the object type `dyn SystemDataSetup`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

This part of your snippet caught my eye:

pub trait PrefabData<'a> {
    type SystemData: SystemData<'a>;
}

This is known in the Rust world as Generalized Associated Types (aka GATs), and an RFC for it can be found here.

Thing is, it's not a stable feature at this time and likely won't be for some time to come.
So unfortunately what you want is not possible, at least not verbatim.

Your best course of action is likely to try and reframe the problem, the solution (e.g. making the implementation less abstract), or both.

I don't think this counts as a GAT because the lifetime parameter is part of the trait definition. If the lifetime bound only appeared in the associated type then I think it would be a GAT, e.g.

pub trait PrefabData {
    type SystemData: SystemData<'a>;
}

The code as written compiles on stable Rust today, so it can't be using any unstable features.

1 Like

The problem is that T::SystemData in register_component does not implement SystemData<'a> for all 'a, as the SystemDataSetup impl on PhantomData<T> requires for its T. PrefabData only requires its SystemData associated type to implement SystemData with the same lifetime as the trait parameter.

You can add this constraint to register_component:

for<'a, 'b> <T as PrefabData<'a>>::SystemData: SystemData<'b>

(Playground)

2 Likes

Wow, that's exactly what I was looking for! Thanks a ton, HRTBs are still a bit of a mystery to me so this is super helpful :grin:

1 Like