Seeking a workaround for HRTB

I want to gather a set of different functions as handlers, and was inspired by the implementation of bevy's ECS system. So I write something like this(playground):

trait Handler<'a>: Send + Sync {
    type Output: Send + 'a;

    fn handle(&self, x: &'a mut u32, y: &'a mut u32) -> Self::Output;
}

trait HandlerFunc<'a, Marker>: Send + Sync {
    type Output: Send + 'a;

    fn handle(&self, x: &'a mut u32, y: &'a mut u32) -> Self::Output;
}

impl<'a, G, T, A> HandlerFunc<'a, for<'any> fn(&'any mut u32, Ref<'any, u32, A>)> for G
where
    G: Fn(&'a mut u32, Ref<'a, u32, A>) -> T + Send + Sync,
    T: Send + 'a,
{
    type Output = T;

    fn handle(&self, x: &'a mut u32, y: &'a mut u32) -> Self::Output {
        (self)(x, Ref(y, PhantomData))
    }
}

pub struct FunctionHandler<F, Marker> {
    func: F,
    marker: PhantomData<fn(Marker)>,
}

impl<'a, G, Marker> Handler<'a> for FunctionHandler<G, Marker>
where
    G: HandlerFunc<'a, Marker>,
{
    type Output = G::Output;

    fn handle(&self, x: &'a mut u32, y: &'a mut u32) -> Self::Output {
        self.func.handle(x, y)
    }
}

trait IntoHandler<'a, O, Marker> {
    type Hf: Handler<'a, Output = O>;

    fn into_handler(self) -> Self::Hf;
}

impl<'a, H: Handler<'a>> IntoHandler<'a, H::Output, ()> for H {
    type Hf = Self;

    fn into_handler(self) -> Self::Hf {
        self
    }
}

pub enum AsHandlerFunc {}
impl<'a, G, Marker> IntoHandler<'a, G::Output, (AsHandlerFunc, Marker)> for G
where
    G: HandlerFunc<'a, Marker>,
{
    type Hf = FunctionHandler<G, Marker>;

    fn into_handler(self) -> Self::Hf {
        FunctionHandler {
            func: self,
            marker: PhantomData,
        }
    }
}

The code above works well. However, when I write a generic function that push a handler to a collection:

type AnyHandler = dyn for<'any> Handler<'any, Output = u32>;

fn push<'a, Marker, H>(handlers: &mut Vec<Box<AnyHandler>>, handler: H)
where
    Marker: 'static,
    H: for<'any> IntoHandler<'any, u32, Marker> + 'static,
{
    handlers.push(Box::new(handler.into_handler()));
}

It gives errors like this:

error: implementation of `Handler` is not general enough
   --> str-match/src/main.rs:119:19
    |
119 |     handlers.push(Box::new(handler.into_handler()));
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `Handler` is not general enough
    |
    = note: `<H as IntoHandler<'2, u32, Marker>>::Hf` must implement `Handler<'1>`, for any lifetime `'1`...
    = note: ...but it actually implements `Handler<'2>`, for some specific lifetime `'2`

error[E0308]: mismatched types
   --> str-match/src/main.rs:119:19
    |
119 |     handlers.push(Box::new(handler.into_handler()));
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
    |
    = note: expected associated type `<<H as IntoHandler<'_, u32, Marker>>::Hf as Handler<'_>>::Output`
               found associated type `<<H as IntoHandler<'_, u32, Marker>>::Hf as Handler<'any>>::Output`

How should I fix the code above?

For anyone who goes into similar problems, here's my solution, axing out IntoHandler's lifetime generics (playground).

trait IntoHandler<Marker> {
    type Hf: for<'any> Handler<'any, Output = Self::Output<'any>>;
    type Output<'a>;

    fn into_handler(self) -> Self::Hf;
}

impl<H: for<'any> Handler<'any>> IntoHandler<()> for H {
    type Hf = Self;
    type Output<'a> = <Self as Handler<'a>>::Output;

    fn into_handler(self) -> Self::Hf {
        self
    }
}

pub enum AsHandlerFunc {}
impl<G, Marker> IntoHandler<(AsHandlerFunc, Marker)> for G
where
    G: for<'a> HandlerFunc<'a, Marker>,
{
    type Hf = FunctionHandler<G, Marker>;
    type Output<'a> = <G as HandlerFunc<'a, Marker>>::Output;

    fn into_handler(self) -> Self::Hf {
        FunctionHandler {
            func: self,
            marker: PhantomData,
        }
    }
}

@js2xxx here is a fix to that problematic function:

fn push<Hf, Marker, H>(handlers: &mut Vec<Box<AnyHandler>>, handler: H)
where
    Marker: 'static,
    Hf: 'static + for<'any> Handler<'any, Output = u32>,
    H: for<'any> IntoHandler<'any, u32, Marker, Hf = Hf> + 'static,
{
    handlers.push(Box::new(handler.into_handler()));
}

Basically, you had a quantification problem with that (removing the Marker and u32 to alleviate syntax):

H : for<'any> IntoHandler<'any, /* Hf: Handler<'any> */>,

alone.

Indeed, this gives you, for any lifetime 'a:

  • a type
    type GetHf<'a, H> = <H as IntoHandler<'a>>::Hf // : Handler<'a>
    

But nothing says that GetHf<'a, H> == GetHf<'b, H>, for instance, so no matter how much freedom you give Rust to pick 'a, it won't be able to find a GetHf<'a, H> so that it be : for<'any> Handler<'any>.

My solution thus works because I'm defining this non-lifetime-generic-and-thus-fixed Hf type parameter, and require that for<'any> GetHf<'any, H> = Hf.

From there, we have:

for<'any>
    Hf = GetHf<'any, H> : Handler<'any>
,

i.e.

Hf = GetHf<'a, H> : Handler<'a>,
Hf = GetHf<'b, H> : Handler<'b>,
Hf = GetHf<'c, H> : Handler<'c>,
…

i.e.

Hf : Handler<'a>,
Hf : Handler<'b>,
Hf : Handler<'c>,
…

i.e.

Hf : for<'any> Handler<'any>

Back to your solution

However, once we've written the snippet I have, we notice we still have that "early" 'a/'any lifetime parameter on IntoHandler, which now plays no role, given that we require a unified associated type anyways.

So if the purpose of IntoHandler is only to let us write this function, then indeed that lifetime parameter on the trait is superfluous and can thus be removed, leading to your solution :slightly_smiling_face:


Aside:

I can't believe you missed the chance to write:

impl<'a, G, A, T>

:stuck_out_tongue_closed_eyes:

3 Likes

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.