Trying to understand Closure lifetimes

Hey there, in the example bellow I'm trying to pass a closure to a method and store it in a Vec:

struct Container {
    actions: Vec<Box<dyn Fn() -> String>>,
}

impl Container {
    fn new() -> Self {
        Self { actions: Vec::new() }
    }
    
    fn add<F: Fn() -> String>(&mut self, f: F) {
      self.actions.push(Box::new(f)); 
    }
}

Bu then I get this error:

error[E0310]: the parameter type `F` may not live long enough
  --> src/main.rs:19:25
   |
19 |       self.actions.push(Box::new(f)); 
   |                         ^^^^^^^^^^^ ...so that the type `F` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
18 |     fn add<F: Fn() -> String + 'static>(&mut self, f: F) {
   |                              +++++++++

For more information about this error, try `rustc --explain E0310`.
error: could not compile `playground` due to previous error

However, the code bellow works:

fn main() {
    let f = || "A String".to_owned();
    
    let mut v: Vec<Box<dyn Fn() -> String>> = Vec::new();
    
    v.push(Box::new(f));
    
    println!("{}", v[0]());
}

So what am I missing here ?

To understand this you first have to know that trait objects, by their very definition, must always have a specific lifetime annotation. The concrete type behind a trait object is unknown to the compiler, thus it can't just infer (from e.g. the type of its fields) what lifetime parameters it has and what concrete lifetimes it carries around.

If you don't supply this lifetime annotation explicitly, it will be inferred by the compiler. In a "global" context, i.e. in items, dyn Trait is actually dyn Trait + 'static unless you specify something else. In a local context, or when the trait object is behind a reference, the inferred lifetime is inferred to be more useful (e.g., &'a dyn Trait is &'a dyn Trait + 'a).

Your first example fails because the field of type Box<dyn Fn> is actually inferred to be Box<dyn Fn + 'static>. In contrast, when it's not a field but a local variable, then Box<dyn Fn + 'some_local_lifetime> will be inferred.

7 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.