Search code examples
rustclosureslifetime

How to handle lifetimes in closures combined with closures as params and return types?


I've tried writing a closure that receives a closure (let's call it A) that returns a closure that receives a value and then applies closure A to it.

Sample:

let do_some = |f: &dyn Fn(u32) -> u32| move |x: u32| f(x);
let result = do_some(&|v: u32| v * 1111)(7);

Observation:

  • the reason the closure f is a &dyn Fn() is because it is the only way the compiler would let me pass a closure to another closure
  • the move operator on the innermost closure is to avoid f being borrowed by the innermost closure

Problem:

error: lifetime may not live long enough
 --> src/main.rs:2:44
  |
2 |     let do_some = |f: &dyn Fn(u32) -> u32| move |x: u32| f(x);
  |                       -                  - ^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
  |                       |                  |
  |                       |                  return type of closure `[closure@src/main.rs:2:44: 2:57]` contains a lifetime `'2`
  |                       let's call the lifetime of this reference `'1`

How to specify lifetime when closures don't allow generic lifetime specification via thee <'a'> notation?


Solution

  • This is a quirk of type inference in closures. The compiler thinks the closure accepts &'a dyn Fn with some specific lifetime 'a, but you actually want it to take &'a dyn Fn for any lifetime 'a (HRTB). This fact causes it to error for reasons I won't explain here (because they're long and complicated).

    On nightly, it is possible to "fix" the wrong lifetime as follows (yep, this is cumbersome):

    #![feature(closure_lifetime_binder, type_alias_impl_trait)]
    
    type RetFn<'a> = impl Fn(u32) -> u32 + 'a;
    let do_some = for<'a> |f: &'a dyn Fn(u32) -> u32| -> RetFn<'a> { move |x: u32| f(x) };
    

    On stable, unfortunately, as far as I know there is no way to fix this error. But you can avoid the problem in the first place by using a Box instead of a reference:

    let do_some = |f: Box<dyn Fn(u32) -> u32>| move |x: u32| f(x);
    let result = do_some(Box::new(|v: u32| v * 1111))(7);
    

    Alternatively, you can box the returned closure, and use a little helper function to help the compiler figure the right lifetime:

    fn force_hrtb<F: Fn(&dyn Fn(u32) -> u32) -> Box<dyn Fn(u32) -> u32 + '_>>(f: F) -> F {
        f
    }
    let do_some = force_hrtb(|f| Box::new(move |x: u32| f(x)));
    let result = do_some(&|v: u32| v * 1111)(7);
    

    I explained a bit more about the problem here.