Capturing outer reference in closure

I'm a bit confused about how capturing works for references in the environment. Take the following example:

fn make_add(l: &i32) -> impl Fn(i32) -> i32 + '_ {
    |r| l + r
}

This doesn't compile because l is borrowed in the closure which outlives the function:

error[E0373]: closure may outlive the current function, but it borrows l, which is owned by the current function

The error message further suggests to use move to make the closure take ownership and this works:

move |r| l + r

But I struggle to understand why exactly move is needed here. It doesn't actually move the object l references into the closure (this construct also works for types that are not Copy) so it seems as if without move, l would be &&i32 inside the closure which would explain the error. However, when asking the compiler to print the type of l (e.g., by writing l.x), it insists it is &i32.

What am I missing here?

You’re almost there. It does “move” l with the move keyword, but “move” actually meany “copy” if the type implements Copy. The type of l is &i32, which is Copy. This is not about i32: Copy, but &T: Copy, for any T. Thus the observation

(I’m assuming you actually meant the observarion that it works for l: &T where T is not Copy.)


This is pretty much true as well, the value inside of the closure will be &&i32, but you’ll be unable to observe this.

The thing is that the closure will capture l by reference, in effect storing a &&i32, internally, but the variable l inside of the closure will still be of type &i32. Think of it as something like

|r| l + r

being replaced by

Closure { l_ref: &l }

for some newly generated type

struct Closure<'a,'b> { l_ref: &'a &'b i32 }

that implements a method like this

impl<'a, 'b> Closure<'a, 'b>
    fn call(&self, r: &i32) {
        *self.l_ref + r
    }
}

and when you have a closure f of this kind, calls to f(r) will be calling this method f.call(r).

So what I’m trying to say is that every use of l in the closure body will be replaced by some dereferenced *self.l_ref internally, which means that you’ll never observe the &&i32.


Also note that the move keyword’s main purpose is basiscally to be used for

  • returning closures from functions, and
  • creating 'static closures for things like thread::spawn

and using move in these two settings will always be either necessary or make no difference. So it’s not a bad idea to just always use the move keyword when you’re returning a closure from a function.

3 Likes

You can read more about closures in my detailed blog:

1 Like

Thanks for the detailed explanation! This clarifies everything for me.

Thanks for sharing this interesting blog post!

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.