I was playing around with trees and traits and encountered a behavior I did not understand. Here is a minimal (not compiling) example:
trait Trait<T> {}
struct Struct<T> {
option: Option<Box<dyn Trait<T>>>, // change this to Option<Box<TestStruct<T>>>
// and it works without issues
}
impl<T> Trait<T> for Struct<T> {}
fn set<T>(s: &mut Struct<T>) { // works when changed to "fn set <T: 'static> ..."
s.option = Some(Box::new(Struct { option: None })) // "error[E0310]: the parameter type `T` may not live long enough"
}
So this code works with either T: 'static
or with Option<Box<TestStruct<T>>>
, but not as is and I could not find a satisfying explanation why.
Can someone explain whats up with traits and lifetimes? Is there an other way to do this?
Whenever we use a trait object, here dyn Trait<T>
, we're giving up a lot of information that Rust normally has about what characteristics a type has.
In particular, the values of a type might contain references, in which case that type has one or more lifetimes that specify how long those references are valid. (The simplest case is when the type is a reference itself; &'a str
can't be around longer than the ending-time identified by 'a
.
But a trait object doesn't specify a concrete type; it could be any type implementing the trait. This means that trait objects must always take into consideration what lifetimes the concrete type is allowed to contain. This is done by the “lifetime bound” of the trait object. Every trait object has a lifetime bound — it's just often unwritten thanks to lifetime elision.
In particular, the elision rules work out to saying that any time you write Box<dyn Trait>
, that's equivalent to Box<dyn Trait + 'static>
. That is, any type X
that you want to turn into this trait object type must meet the bound X: 'static
, which means that if it contains any lifetimes, they must outlive 'static
— which in the specific case of the 'static
lifetime, is the same as saying that they must be equal to 'static
, because no lifetime is longer than 'static
.
(The other common case for default trait object lifetime bounds is &'a dyn Trait
, which the rules make equivalent to &'a (dyn Trait + 'a)
— allowing anything that outlives or equals the lifetime of the reference.)
So, explaining what you observed:
Adding T: 'static
works because your TestStruct<T>
(which you did not give the definition of, so I'm guessing) contains no references, so T: 'static
logically implies TestStruct<T>: 'static
, which satisfies the trait object lifetime bound.
Using Option<Box<TestStruct<T>>>
instead of Option<Box<dyn Trait<T>>>
means that there is no trait object involved, so there is no default 'static
bound, so Struct<T>
is entirely generic — it doesn't care whether T
has any lifetimes or not.
In most cases when you would want to use Box<dyn Trait>
— to store it in some data structure — the T: 'static
bound is the right choice, because things that you are going to store for later usually need to be not tied to a specific lifetime — able to live forever. But if you did need to admit types that have non-static lifetimes, you can always write Box<dyn Trait + 'a>
instead (provided that the lifetime 'a
is declared).