Closure may outlive a static reference?

I have this little toy function:

 fn call<F: Fn()>(f: &'static F) -> Box<dyn Fn()> { Box::new( || { f() })}

The compiler says "closure may outlive the current function, but it borrows f, which is owned by the current function."

First, I am confused as to what it means that f is "owned by the current function", since f is just a reference and so there is no ownership here.

Second, f has a 'static lifetime. Why should the compiler worry that the closure may outlive it?

Try running this alternate code; (Could rename the shadow f to borrow_f if needing to be clearer.)

fn call<F: Fn()>(f: &'static F) -> &&'static F { let f = &f; return f; }

The closure you create does essentially the same. If instead you use move || there isn't such borrow created.

(p.s. return added for extra visibility, usually would not use.)

1 Like

Hmm, are you suggesting that without move, the closure is borrowing the reference (instead of the function itself) and that reference has a lifetime of its own aside from the lifetime of the value it borrows? I initially thought that, but in my snippet, f in the closure seems to have type &'static F and not &&'static F.

Yes, that's correct.

This is also true. The body of the closure uses f however it wants, and the compiler analyzes it to decide whether to implicitly borrow or move f. In this case because the body of the closure only calls f, which doesn't require moving it, the compiler decides the closure doesn't need ownership. move basically tells the compiler "No, I actually want you to move all the captured variables into this closure, even if the body only uses them by reference".

I was going to add a post similar to this, saying that this should work too because FnOnce's call is self bound, but then I ran into this weird behavior:

fn call<F>(func: &'static F) -> Box<dyn FnOnce()> where for<'a> &'a F: FnOnce() {
    Box::new(|| func())

Seems like it would be fine, &'static F matches for<'a> &'a F, and therefore &'static F: FnOnce(), but yet the compiler disagrees.

Any ideas as to why?

1 Like

Yes, this is true. You can read about the precise rules for how closures work in Rust in my blog post

1 Like

Your code works, but for some weird reason Rust needs some help in understaing which specific type and/or which specific trait the func() call expression is using.
With feature(fn_traits), for instance, using the fully-qualified call syntax makes your code work.

And in stable Rust, a helper function achieves what that fully-qualified syntax does:

fn call<F> (func: &'_ F)
  -> Box<dyn Fn() + '_>
    // equivalent to `F : Fn()`
        &'a F : FnOnce()
    return Box::new(move || do_call(func));
    // where
    fn do_call (f: impl FnOnce()) { f() }

As to why Rust "brain-farts" on that one, I don't really know :woman_shrugging:

1 Like