Attila Kun
Attila Kun

Reputation: 2405

"temporary value dropped while borrowed" with capturing closure

Please consider the following example (playground):

struct Animal<'a> {
    format: &'a dyn Fn() -> (),
}

impl <'a>Animal<'a> {
    pub fn set_formatter(&mut self, _fmt: &'a dyn Fn() -> ()) -> () {} // Getting rid of 'a here satisfies the compiler
    pub fn bark(&self) {}
}

fn main() {
    let mut dog: Animal = Animal { format: &|| {()} };
    let x = 0;
    dog.set_formatter(&|| {
        println!("{}", x); // Commenting this out gets rid of the error. Why?
    });
    dog.bark(); // Commenting this out gets rid of the error. Why?
}

This gives the following compilation error:

Compiling playground v0.0.1 (/playground)
error[E0716]: temporary value dropped while borrowed
  --> src/main.rs:13:24
   |
13 |       dog.set_formatter(&|| {
   |  ________________________^
14 | |         println!("{}", x); // Commenting this out gets rid of the error. Why?
15 | |     });
   | |     ^ - temporary value is freed at the end of this statement
   | |_____|
   |       creates a temporary which is freed while still in use
16 |       dog.bark(); // Commenting this out gets rid of the error. Why?
   |       --- borrow later used here
   |
   = note: consider using a `let` binding to create a longer lived value

error: aborting due to previous error

For more information about this error, try `rustc --explain E0716`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

This kind of makes sense as the closure that I'm passing to dog.set_formatter(...) is indeed a temporary which (I guess) is freed when execution proceeds to dog.bark();.

I know that getting rid of the explicit lifetime annotation in the implementation of set_formatter seems to satisfy the compiler (note the missing 'a before dyn):

pub fn set_formatter(&mut self, _fmt: & dyn Fn() -> ()) -> () {}

However, I don't understand the following:

  1. Why does the problem go away when I comment out println!("{}", x); inside the closure? I'm still passing a temporary which I expect the compiler to complain about, but it doesn't.
  2. Why does the problem go away when I comment out dog.bark(); at the end? Again, I'm still passing a temporary closure which is freed but now the compiler is happy. Why?

Upvotes: 1

Views: 1008

Answers (1)

pretzelhammer
pretzelhammer

Reputation: 15105

The first thing to understand is that &|| () has a 'static lifetime:

fn main() {
    let closure: &'static dyn Fn() = &|| (); // compiles
}

Another thing worth mentioning is that a closure's lifetime cannot exceed the lifetime of any of the variables it captures from its environment, which is why if we try to pass a non-static variable to our static closure it would fail to compile:

fn main() {
    let x = 0; // non-static temporary variable
    let closure: &'static dyn Fn() = &|| {
        println!("{}", x); // x reference captured by closure
    }; // error: trying to capture non-static variable in static closure
}

We'll come back to that. Anyway, so if I have a struct which is generic over references, and I pass it a 'static reference, I'll have a 'static instance of that struct:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

fn main() {
    let dog: Dog<'static> = Dog { format: &|| () }; // compiles
}

The second thing to understand is once you instantiate a type you cannot change it. This includes any of its generic parameters, including lifetimes. Once you have a Dog<'static> it will always be a Dog<'static>, you cannot convert it into a Dog<'1> for some anonymous lifetime '1 that is shorter than 'static.

This has some strong implications, one of which is that your set_formatter method probably doesn't behave how you think it behaves. Once you have a Dog<'static> you can only pass 'static formatters to set_formatter. The method looks like this:

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

But since we know we're working with a Dog<'static> we can substitute out the generic lifetime parameter 'a with 'static to see what we're really working with:

// what the impl would be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}

So now that we've gotten all of that context out of the way let's get to your actual questions.

Why does the problem go away when I comment out println!("{}", x); inside the closure? I'm still passing a temporary which I expect the compiler to complain about, but it doesn't.

Why it fails, with comments:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

fn main() {
    let mut dog: Dog<'static> = Dog { format: &|| () };
    
    // x is a temporary value on the stack with a non-'static lifetime
    let x = 0;
    
    // this closure captures x by reference which ALSO gives it a non-'static lifetime
    // and you CANNOT pass a non-'static closure to a Dog<'static>
    dog.set_formatter(&|| {
        println!("{}", x); 
    });
}

The reason this error is "fixed" by commenting out the line println!("{}", x); is because it gives the closure a 'static lifetime again since it no longer borrows the non-'static variable x.

Why does the problem go away when I comment out dog.bark(); at the end? Again, I'm still passing a temporary closure which is freed but now the compiler is happy. Why?

This weird edge case only seems to happen when we don't explicitly type-annotate the dog variable with Dog<'static>. When a variable doesn't have an explicit type annotation the compiler attempts to infer its type, but it does so lazily, and tries to be as flexible as possible, giving the benefit of the doubt to the programmer, in order to make the code compile. It really should throw a compile error even without the dog.bark() but it doesn't for whatever mysterious reasons. The point is it's not the dog.bark() line that is making the code fail to compile, it should be failing to compile at the set_formatter line regardless, but for whatever reason the compiler doesn't bother to throw an error until you try to use dog again after that offending line. Even just dropping dog will trigger the error:

struct Dog<'a> {
    format: &'a dyn Fn(),
}

impl<'a> Dog<'a> {
    fn set_formatter(&mut self, _fmt: &'a dyn Fn()) {}
}

fn main() {
    let mut dog = Dog { format: &|| () };
    let x = 0;
    
    dog.set_formatter(&|| {
        println!("{}", x); 
    });
    
    drop(dog); // triggers "temp freed" error above
}

And since we've come this far, let's answer your unofficial third question, paraphrased by me:

Why does getting rid of the 'a in the set_formatter method satisfy the compiler?

Because it changes what is effectively this for Dog<'static>:

// what the impl would be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &'static dyn Fn()) {}
}

Into this:

// what the impl would now be for Dog<'static>
impl Dog {
    fn set_formatter(&mut self, _fmt: &dyn Fn()) {}
}

So now you can pass non-'static closures to a Dog<'static> although it's pointless since the method doesn't actually do anything, and the compiler will complain again the moment you actually try to set the closure in the Dog<'static> struct.

Upvotes: 6

Related Questions