Bounding generic parameter to HRTB

Hello,

I want to create a trait that defines in which way a parameter T is passed to a Fn after it was passed by value:

trait Foo{
    type Bar<'a, T: 'a>;
    fn foo<T, FN: for<'a> Fn(Self::Bar<'a, T>)>(t: T, f: FN);
}
impl Foo for (){
    type Bar<'a, T: 'a> = &'a T;
    fn foo<T, FN: for<'a> Fn(Self::Bar<'a, T>)>(t: T, f: FN){
        f(&t);
    }
}

But it does not work out:

error[E0311]: the parameter type `T` may not live long enough
  --> src/lib.rs:10:8
   |
10 |     fn foo<T, FN: for<'a> Fn(Self::Bar<'a, T>)>(t: T, f: FN){
   |        ^^^ ...so that the type `T` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
10 |     fn foo<'a, T: 'a, FN: for<'a> Fn(Self::Bar<'a, T>)>(t: T, f: FN){
   |            +++  ++++

error: could not compile `playground` due to previous error

I am quite sure the issue is I can't bound T: 'a in the generic parameter list of foo while keeping 'a as a HRTB.

My approach to find the solution led me to this function:

fn foo<T, FN: Fn(&T)>(t: T, f: FN){
    f(&t);
}

And trying to desugar it to the point where I can see how the lifetimes and bounds to T work. But I only got so far which yet lacks bounds to T:

fn foo<T, FN: for<'a> Fn(&'a T)>(t: T, f: FN){
    f(&t);
}

I know T is not bounded, but I need to be able to prove that to the compiler with my trait, like this function here is be able to.

What could I do here?

I attempted to take T by reference (&mut T because Foo::Bar might require that in another impl) but all ways I got it compile made me deal with the very same error at the calling site.

Yeah, you'd like to express for<'a where T : 'a> … Self::<'a, T>, but you end up with:

for<'a> Self::<'a, T /* where T : 'a */>

which implies T : for<'a> 'a i.e., T : 'static.


Right now, there aren't any nice solutions for this, only a hack using higher-kinded types which takes advantage of well-formedness of things such as &'a T only being checked on use, not on definition, which allows skipping that problematic explicit T : 'a bound:

trait Foo {
 // type Bar<T> : <'a>; /* i.e., type Bar<T><'a>; */
    type Bar<T> : HKT;

    fn foo<T, F> (t: T, f: F)
    where
        F : for<'a> Fn(Apply!(Self::Bar<T><'a>)),
    ;
}

impl Foo for () {
 // type Bar<T><'a> =       &'a T;
    type Bar<T> = HKT!(<'a> &'a T);

    fn foo<T, F> (t: T, f: F)
    where
        F : for<'a> Fn(Apply!(Self::Bar<T><'a>)),
    {
        f(&t);
    }
}
1 Like

Thanks!

I am not sure I want to bring features into my project that seem far away from being stabilized. In the end I only want to implement Foo on two types to use it as a bound for an associated type of another trait.

I guess to not lock myself to nightly instead I have two parent traits so I don't need Foo at all. It will lead to some more repeated code but I guess I can minimize that.

Still this solution here teaches me a few new things and I am glad I got across that. :slight_smile:

Are you talking of trait_alias? That's just for the Playground demo, if you look at that link
to lending-iterator's helpers, you'll see it's a 100% stable Rust crate and design :slightly_smiling_face:

Oh see, I yet need to read it a few more times to grasp it. :smile:

<F as Foo>::Bar<T> will be a parameter of a few trait function the user of my crate has to implement, will this lead to inconveniences I should be aware of?

ah, in that case kind of, yeah. Basically implementing the HKT needs that HKT! macro that you saw. So you'd have to tell them to write:

type Bar<T> = HKT!(<'a> => &'a T);

rather than the more intuitive:

type Bar<'a, T : 'a> = &'a T;

that you had.


In that case, you may legitimately decide not to use this (macros are decent when used internally, but exposing it to users is not that nice). What's the more realistic code that would fit your use case? Maybe there is a way to XY this / to rewrite the trait in a way that circumvents the problem

It is for my Bevy project that logs mutations done in a system. In one variant there is one log per system (ResMut<Log>) and one per entity (Query<(Q, &mut Log)> with Q being other user-defined query items). Both of them allow the user to include other system parameters (S below), but only the first allows &mut access to these parameters. The other only exposes other params by non-mutable reference because the mutation in the system happens in the query iteration. It just makes no sense at best and makes parallel iteration impossible at worst.

Here is code I drafted to show my issue to the bevy community:
(which is even more wrong than my OP here because I was further away from the solution a few days ago)

use bevy::{ecs::{query::{QueryItem, WorldQuery},system::SystemParam},prelude::{Component, Query, ResMut}};

#[derive(Component)]
struct Log;
struct PerSystem;
struct PerEntity<Q: WorldQuery + 'static, const BATCH_SIZE: usize = 0>(std::marker::PhantomData<Q>);

trait LogPosition {
    type Params<'w, 's>: SystemParam;
    type UserParams<'w, 'a, S: SystemParam + Send + Sync + 'a>;
    fn mutate<
        'w, 's, 'a,
        S: SystemParam + Send + Sync + 'a,
        FN: Fn(&mut Log, Self::UserParams<'w, 'a, S>) + Send + Sync + Copy,
    >(params: Self::Params<'w, 's>, s: S, f: FN);
}

impl LogPosition for PerSystem {
    type Params<'w, 's> = ResMut<'w, Log>;
    type UserParams<'w, 'a, S: SystemParam + Send + Sync + 'a> = S;
    fn mutate<
        'w, 's, 'a,
        S: SystemParam + Send + Sync + 'a,
        FN: Fn(&mut Log, Self::UserParams<'w, 'a, S>) + Send + Sync + Copy,
    >(mut params: Self::Params<'w, 's>, mut s: S, f: FN) {
        f(&mut params, s);
    }
}

impl<Q: WorldQuery, const BATCH_SIZE: usize> LogPosition for PerEntity<Q, BATCH_SIZE> {
    type Params<'w, 's> = Query<'w, 's, (Q, &'static mut Log)>;
    type UserParams<'w, 'a, S: SystemParam + Send + Sync + 'a> = (&'a S, QueryItem<'w, Q>);
    fn mutate<
        'w, 's, 'a,
        S: SystemParam + Send + Sync + 'a,
        FN: Fn(&mut Log, Self::UserParams<'w, 'a, S>) + Send + Sync + Copy,
    >(mut params: Self::Params<'w, 's>, s: S, f: FN) {
        if BATCH_SIZE == 0 {
            params.for_each_mut(|(item, mut log)| {
                f(&mut log, (&s, item));
            });
        } else {
            params.par_for_each_mut(BATCH_SIZE, |(item, mut log)| {
                f(&mut log, (&s, item));
            });
        }
    }
}

UserParams is what Bar is in my OP. The user does not construct them, but has to use them in trait functions. And closures I put in f calls the user-defined function in turn by passing through the UserParams, leaving me to deal with mutating Log and other stuff.

And that is where code duplication is prevented if there is only one trait the user has to define, with setting an associated type to either PerSystem or PerEntity<Q>.

But I think I can still minimize that with intermediate functions.

To be honest I am not sure someone else than me uses this but I myself want to have a clean API too. :smiley:

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.