Lifetime issue in closure

I am trying to understand why the following code is invalid. The error message is complaining that the temporary x is dropped while still in use, but I don't understand how it would still be in use. When the function f returns there should be no borrows of x, so it should be safe to drop. What does it think the lifetime 'a is?

fn foo<'a, F: Fn(&'a usize)>(f: F) {
    let x = &mut 0_usize;
    f(x);
}

Error:

error[E0716]: temporary value dropped while borrowed
  --> src/arena.rs:43:18
   |
1  | fn foo<'a, F: Fn(&'a usize)>(f: F) {
   |        -- lifetime `'a` defined here
2  |     let x = &mut 0_usize;
   |                  ^^^^^^^ creates a temporary which is freed while still in use
3  |     f(x);
   |     ---- argument requires that borrow lasts for `'a`
4  | }
   | - temporary value is freed at the end of this statement

When a named lifetime is generic on a function like this, the caller of the function determines what exactly 'a is. In effect, that function signature is saying "I can do something with a Fn(&'a usize), no matter how long or short 'a is. Anything you got!" And as such, I could call it like so, say:

    foo(|u: &'static usize| {
        std::thread::spawn(move || println!("On another thread now: {u}"))
            .join()
            .unwrap();
    });

And now 'a must be 'static in this invocation of foo. Maybe that thread holds onto &x forever.

Playground to start from.


Now, how to fix it. The main approach is, just don't name the lifetime in the closure argument like this. Instead do:

-fn foo<'a, F: Fn(&'a usize)>(f: F) {
+fn foo<F: Fn(&usize)>(f: F) {

Which is short for:

fn foo<F: for<'any> Fn(&'any usize)>(f: F) {

Note how now it's the caller of foo that's getting an error, and things compile if you comment out the body of main. This signature says, "You have to give me a closure that can take any lifetime, not just one specific one that you pick. I'll pick whatever lifetime(s) I want to call your closure with." The thread-spawning closure only works with 'static, and hence the error in main.

More on for<'any>, aka higher-ranked trait bounds (HRTB), here. You can right it out if you want to. But usually you can just leave the lifetime anonymous to get one.


The other thing you could do was just send a &0 or not take a &mut 0, because non-mutable integers can be promoted to statics, and then the &'static usize can be coerced into any &'a usize. But I'm 99.8% sure that was just for illustration.

3 Likes

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.