Why is this HRBT not satisfied?

I am using specs crate to do some work. The feature I am implementing is to statistic time cost by all the systems, so I rewrite the add function like this

pub struct EcsDispatcherBuilder<'a, 'b> {
    builder: DispatcherBuilder<'a, 'b>,
    profile: bool,
}

impl<'a, 'b> EcsDispatcherBuilder<'a, 'b> {
pub fn add<T>(&mut self, system: T, name: &str, dep: &[&str])
    where
        T: for<'c> System<'c> + Send + 'a,
        for<'c> <T as System<'c>>::SystemData: SystemData<'c>,
    {
        if self.profile {
            self.builder
                .add(StatisticSystem(name.into(), system), name, dep);
        } else {
            self.builder.add(system, name, dep);
        }
    }
}

And a normal system like this

impl<'a> System<'a> for CloseSystem {
    type SystemData = (
        Entities<'a>,
        WriteStorage<'a, Closing>,
        ReadStorage<'a, NetToken>,
        Read<'a, LazyUpdate>,
        Read<'a, BytesSender>,
    );
...
}

I am sure CloseSystem::SystemData implements SystemData, because System::SystemData is bounded to DynamicSystemData, and all DynamicSystemData implements SystemData, CloseSystem compiles ok until I really call an add function like this.

builder.add(CloseSystem, "close", &[]);
error[E0277]: the trait bound `for<'c> <_ as specs::System<'c>>::SystemData: SystemData<'c>` is not satisfied
   --> src\lib.rs:235:17
    |
235 |         builder.add(CloseSystem, "close", &[]);
    |                 ^^^ the trait `for<'c> SystemData<'c>` is not implemented for `<_ as specs::System<'c>>::SystemData`

Can somebody help me out? Thanks.

I'm not sure I fully understand the example without the definition of trait SystemData, but if you are absolutely sure the code is correct, then maybe it's this compiler bug?


Also, where does 'a come from? Maybe you meant SystemData<'a> instead?

'a is another lifetime in Self, I have added that in question.
For convenience, I put all the related trait links below.
SystemData
System

Yeah this is a "lazy normalization" bug which currently makes the HRTB experience and story of Rust quite painful; the workaround is to preemptively embed the bound on the assoc type within the trait definition itself, since that's not higher-order. That way the higher-order bound is of the kind for<'lt> Ty : Trait<'lt>, with no explicit higher-order bound on the assoc type.

Thus:

trait System<'a> {
-   type SystemData;
+   type SystemData: SystemData<'a>;
    …
}

(which, by the way, given the name of the assoc type, here seems like a sensible thing to do).

  • EDIT: just noticed these are not your traits but the ones from ::spec. The following thus very much applies :slightly_smiling_face:

If you can't (or don't want to) touch System, you can use your own helper trait:

trait MySystem<'a> {
    type SystemData : SystemData<'a>;
    …
}

impl<'a, T : ?Sized> MySystem<'a> for T
where
    T : System<'a>,
    // non-higher-order so it's OK.
    <T as System<'a>>::SystemData : SystemData<'a>,
{
    type SystemData = <T as System<'a>>::SystemData;
    …
}

And at that point using T : for<'__> MySystem<'__> will Just Work™ :slightly_smiling_face:


Minimal repro / demo

trait Trait<'__> {
    type Assoc;
}
impl Trait<'_> for u8 {
    type Assoc = i8;
}

const _FAILS: () = {
    fn f<T> ()
    where
        // You can even uncomment this!
        T : for<'lt> Trait<'lt /*, Assoc = i8 */>,
        for<'lt>
            <T as Trait<'lt>>::Assoc : Copy
        ,
    {}
    
    // Error: `for<'lt> Copy` is not implemented for `<u8 as Trait<'lt>>::Assoc`
    let _ = f::<u8>;
};

const _OK: () = {
    trait MyTrait<'__> {
        type Assoc : Copy;
    }
    impl<'lt, T : ?Sized> MyTrait<'lt> for T
    where
        T : Trait<'lt>,
        <T as Trait<'lt>>::Assoc : Copy,
    {
        type Assoc = <T as Trait<'lt>>::Assoc;
    }
    
    fn f<T> ()
    where
        T : for<'lt> MyTrait<'lt>,
    {
        let it = None::< <T as MyTrait<'_>>::Assoc >;
        drop((it, it)); // <- proof that `it` is indeed `Copy`.
    }
};
3 Likes

Thanks for the reply, the HRTB problem was fixed, but another compiler error ocurre

pub struct StatisticSystem<T>(pub String, pub T);

impl<'a, T> System<'a> for StatisticSystem<T>
where
    T: System<'a> + GameSystem<'a>,
{
    type SystemData = (
        ReadExpect<'a, TimeStatistic>,
        <T as GameSystem<'a>>::SystemData,
    );

    fn run(&mut self, (ts, data): Self::SystemData) {
        let begin = UNIX_EPOCH.elapsed().unwrap();
        self.1.run(data);
        let end = UNIX_EPOCH.elapsed().unwrap();
        ts.add_time(self.0.clone(), begin, end);
    }
}

error[E0308]: mismatched types
   --> src\system.rs:391:20
    |
391 |         self.1.run(data);
    |                    ^^^^ expected specs::System::SystemData, found GameSystem::SystemData
    |
    = note: expected associated type `<T as specs::System<'a>>::SystemData`
               found associated type `<T as GameSystem<'_>>::SystemData`

If I change the T to System, SystemData bound will not be satisfied. What should I do now?

Edit: I found a way to solve this,

pub trait GameSystem<'a> {
    type SystemData: SystemData<'a>;

    fn run(&mut self, data: Self::SystemData);
}

impl<'a, T: ?Sized> GameSystem<'a> for T
where
    T: System<'a>,
    <T as System<'a>>::SystemData: SystemData<'a>,
{
    type SystemData = <T as System<'a>>::SystemData;

    fn run(&mut self, data: Self::SystemData) {
        System::run(self, data);
    }
}

Proxy can do the trick.

I wonder if:

impl<'a, T> System<'a> for StatisticSystem<T>
where
    T : GameSystem<'a>,
-   T: System<'a>,
+   T: System<'a, SystemData = <T as GameSystem<'a>>::SystemData>,

would work here :thinking:

Actually, more generally, that impl does not involve a higher-rank lifetime, so a good old <T as System<'a>>::SystemData : SystemData should suffice :slightly_smiling_face:

I tried this but it failed as before.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.