Search code examples
rustclosureslifetimecapture

In Rust, can I return FnMut with a captured mutable reference, perhaps by specifying a generic lifetime?


Is it possible to specify a generic lifetime on FnMut in Rust for a function like this?

fn f1<'a, FRet:'a + FnMut() -> ()>(v: &'a mut i32) -> FRet {
   let mut fret = || {
       let i = v;
       println!("inside");
       // lots of complicated logic on captured var
       *i = 5;
   };

   return fret;
}

Right now I'm getting an error, that return is incompatible with FRet.


Solution

  • Your code has several issues. One is that you cannot make the function that returns a closure have a generic return type. f1 being generic over FRet means that the caller gets to choose the FRet, and that obviously can't work when you're returning a closure you implement, and not something the caller can or should specify. This is why the compiler complains that FRet is incompatible with what you're actually returning.

    The way to return a closure is by either boxing it or using the impl Trait return type that declares an unnamed type returned by the function:

    fn f1(v: &mut i32) -> impl FnMut() {
       let fret = || {
           let i = v;
           println!("inside");
           // lots of complicated logic on captured var
           *i = 5;
       };
    
       fret
    }
    

    The above still doesn't compile with the compiler complaining that the closure is FnOnce rather than FnMut. This is because &mut i32 is not Copy, so let i = v actually moves the reference out of v which makes the closure callable only once. That can be fixed with a reborrow, i.e. changing let i = v to let i = &mut *v.

    After that the compiler will complain that the lifetime of reference is not captured by the closure, and that you can fix that by adding + '_. (This is equivalent to declaring a lifetime 'a and declaring v: &'a mut i32 and impl FnMut() + 'a.) With this change the code compiles:

    fn f1(v: &mut i32) -> impl FnMut() + '_ {
       let fret = || {
           let i = &mut *v;
           println!("inside");
           // lots of complicated logic on captured var
           *i = 5;
       };
    
       fret
    }
    

    Playground