Why doesn't this code pass the borrow checker? (Error E0310)

Consider this example code:

trait ExampleTrait{}

struct TraitContainer{
    trait_object: Box<dyn ExampleTrait>
}

impl TraitContainer{

    fn new<T: ExampleTrait>(trait_object: T) -> TraitContainer{
        TraitContainer{
            trait_object: Box::new(trait_object)
        }
    }

}

fn main() {

}

This code seems like it should be able to pass the borrow checker because any value that is passed into TraitContainer::new will be moved into that function, and then into the returned struct, where it then waits until it is moved out or that struct is dropped — there are no references at all that may live longer than their referants. Yet, the borrow checker complains:

error[E0310]: the parameter type `T` may not live long enough
      --> src/main.rs:11:27
       |
    9  |     fn new<T: ExampleTrait>(trait_object: T) -> TraitContainer{
       |            -- help: consider adding an explicit lifetime bound...: `T: 'static +`
    10 |         TraitContainer{
    11 |             trait_object: Box::new(trait_object)
       |                           ^^^^^^^^^^^^^^^^^^^^^^
       |
    note: ...so that the type `T` will meet its required lifetime bounds
      --> src/main.rs:11:27
       |
    11 |             trait_object: Box::new(trait_object)
       |                           ^^^^^^^^^^^^^^^^^^^^^^

This complaint (and associated explanation via rustc --explain or the website) talks about lifetime parameters on a type though, which doesn't make any sense. Lifetime parameters refer to specific sections of memory, but types are a contract for how to use sections of memory. If a trait exists in the code, then it always exists — there is no way that a trait can stop existing during the runtime of a program, so why would it have a lifetime?

A bound on a type parameter limits which types can appear in that place. In particular, a lifetime bound disallows types that contain reference with a shorter lifetime. If T: 'a, then, for example, you can only use T = &'b U if b lifetime is not shorter than a. The T: 'static bound means that values of type T must not contain any data with a limited lifetime, i.e. they must own the data or point to 'static data (like a string literal).

You can have boxed trait objects with limited lifetime via Box<dyn Trait + 'a> syntax. If the lifetime is omitted, the static lifetime is assumed. So you're trying to create a trait object with a static lifetime, but the input value of type T can potentially contain references with a shorter lifetime, unless you add T: 'static bound.

2 Likes

Do you envisage a case where you want the trait_object to contain references, or will it always own all its data? If the latter, then just add the 'static bound as the compiler suggests, if the former then you need to tell the compiler how to link up the lifetimes

struct TraitContainer<'a> {
    trait_object: Box<dyn ExampleTrait + 'a>
}

impl<'a> TraitContainer<'a> {

    fn new<T: ExampleTrait + 'a>(trait_object: T) -> Self {
        TraitContainer {
            trait_object: Box::new(trait_object)
        }
    }
}

play.rust-lang.org example

1 Like

Thanks @derekdreery and @Riateche. That answers my question :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.