Higher order functions supporting possible lifetimes

Viewed 131

Suppose I have a higher order function, such as composition from this answer:

fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
where
    F: Fn(A) -> B,
    G: Fn(B) -> C,
{
    move |x| g(f(x))
}

and I want to partially apply it, as in

fn id<T>(x: &T) -> &T { x }

fn compose_with_id<T, U, F>(f: F) -> impl Fn(&T) -> U
where
    F: Fn(&T) -> U
{
    compose(id, f)
}

This doesn't compile. IIUC the issue is that at the compose callsite, we must substitute a specific type for A (including a specific lifetime), but the return value needs to implement Fn(&'a T) -> U for all 'a. I tried moving the call to compose into a closure:

fn compose_with_id<T, U, F>(f: F) -> impl Fn(&T) -> U
where
    F: Fn(&T) -> U
{
    move |t| compose(id, f)(t)
}

This also doesn't compile. The problem is now that compose is trying to take ownership of f each time the resulting function is called. I don't see a way to fix compose_with_id (without insisting F: Copy); compose takes ownership of f, which can only happen once, so we must choose one lifetime, but the return value needs to handle any lifetime.

We could create a variant of compose that works for references:

fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(&A) -> C
where
    F: Fn(&A) -> &B,
    G: Fn(&B) -> C,
{
    move |x| g(f(x))
}

(whose return value has a HRTB) but it can't be used for non-reference inputs.

How can I define compose such that it can be partially applied to non-copyable functions with or without reference inputs?

1 Answers

How about add lifetime annotations to compose_with_id?

fn compose_with_id<'a, T, U, F>(f: F) -> impl Fn(&'a T) -> U
where
    T: 'a,
    U: 'a,
    F: Fn(&'a T) -> U,
{
    compose(id, f)
}
Related