Search code examples
genericsrusttraitslifetime

In Rust, Why does generics needs to specify lifetime as `'static` when passed as an argument?


struct TestStruct<F> {
    param: F,
}
trait TestTrait {}
impl<F> TestTrait for TestStruct<F> {}

fn t<F>(input: F) -> Box<dyn TestTrait> {
    Box::new(TestStruct { param: input })  // error here
}
error[E0310]: the parameter type `F` may not live long enough
  --> src/main.rs:22:5
   |
22 |     Box::new(TestStruct { param: input }) //
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |     |
   |     the parameter type `F` must be valid for the static lifetime...
   |     ...so that the type `F` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
21 | fn t<F: 'static>(input: F) -> Box<dyn TestTrait> {
   |       +++++++++

For more information about this error, try `rustc --explain E0310`.

This seems to happens only for generics, i tried input: Box<dyn trait>/input: Struct and some other types, maybe i'm wrong about only for generics.

Add 'static to fn t<F: 'static> works. And '_ works inside traits:

struct TestStruct<F> {
    param: F,
}
trait TestTrait {}
impl<F> TestTrait for TestStruct<F> {}

impl<F> TestStruct<F> {
    fn t(&self, input: F) -> Box<dyn TestTrait + '_> {  // add `'_` here will compile, but not for the example above
                                                        // is it the same as adding `'static` at `impl<F: 'static>`?
        Box::new(TestStruct { param: input })
    }
}

I don't understand why it's needed and why 'static.


Solution

  • Consider what would happen if your t function is invoked with an input that contains a borrow of shorter lifetime than 'static:

    let s = String::from("foo");
    let boxed_trait_object = t(s.as_str());
    drop(s);
    

    When s is dropped, boxed_trait_object contains a reference to freed memory: undefined behaviour. The compiler sees that the returned trait object is not constrained by any lifetime and therefore requires F: 'static to prevent situations like the above.

    However, we can instead add a lifetime parameter to the function signature:

    fn t<'a, F: 'a>(input: F) -> Box<dyn 'a + TestTrait> {
        Box::new(TestStruct { param: input })
    }
    

    Here we are saying that the returned trait object is constrained to the lifetime of the borrows in input (if any).

    See it on the playground.