Description
I wrote a lot about this here, but I will recreate the demonstrations in this issue.
Some familiarity with how type bounds work with the default lifetime is important to understand this issue. In particular, references do have the type bound.
// Notionally
builtintype<'a, T: 'a + ?Sized> &'a T;
Factual corrections
&dyn Trait
default lifetime can be inferred in expressions
The reference says the default is only inferred in expressions if there are no type bounds and the default outside of expressions is 'static
, but this is not true.
Example
This example compiles. Playground.
trait Trait {}
impl Trait for () {}
fn example() {
let local = ();
// The outer reference lifetime cannot be `'static`...
let obj: &dyn Trait = &local;
// Yet the `dyn Trait` lifetime is! Thus the default must be inferred
let _: &(dyn Trait + 'static) = obj;
}
I believe it is due to this change (accepted without FCP).
Note, however, that if you annotate a named lifetime for the generic type (here the reference lifetime), then the default is that named lifetime. This is the behavior the reference currently says always happens.
Example
This example does not compile. Playground.
trait Trait {}
impl Trait for () {}
fn example<'a>(arg: &'a ()) {
let dt: &'a dyn Trait = arg;
// fails
let _: &(dyn Trait + 'static) = dt;
}
The default lifetime for types with multiple bounds can be inferred in expressions
The reference says that for types with multiple bounds, an explicit bound must be specified. However, this is not true in expressions.
Example
This example compiles. Playground.
trait Trait {}
impl Trait for () {}
struct Weird<'a, 'b, T: 'a + 'b + ?Sized>(&'a T, &'b T);
fn example<'a, 'b>() {
// Either of `dyn Trait + 'a` or `dyn Trait + 'b` is an error,
// so the `dyn Trait` lifetime must be inferred independently
// from `'a` and `'b`
let _: Weird<'a, 'b, dyn Trait> = Weird(&(), &());
}
Trait bounds can override type bounds
According to the reference, trait bounds aren't considered for the default lifetime if the type has bounds. That is, according to the reference, type bounds override trait bounds. However, this is not true: trait bounds can override type bounds.
(When exactly this happens is complicated; see further below.)
Examples
This example compiles. Playground.
pub trait LifetimeTrait<'a>: 'a {}
impl LifetimeTrait<'_> for () {}
// n.b. `'a` is invariant due to being a trait parameter
fn fp<'a>(t: &(dyn LifetimeTrait<'a> + 'a)) {
f(t);
}
// The fact that `fp` compiled means that the trait bound does in fact apply,
// and results in a trait object lifetime independent of the reference lifetime.
//
// That is, the type is not `&'r (dyn LifetimeTrait<'a> + 'r)`;
// the trait bound applied despite the presence of a type bound
pub fn f<'a: 'a>(_: &dyn LifetimeTrait<'a>) {}
This example compiles, but fails with the commented change. Playground.
use core::marker::PhantomData;
// Remove `: 'a` to see the compile error
pub trait LifetimeTrait<'a>: 'a {}
// This type has bounds; moreover, according to the reference, an explicit
// trait object lifetime is required due to having multiple bounds.
pub struct Over<'a, T: 'a + 'static + ?Sized>(&'a T);
pub struct Invariant<T: ?Sized>(*mut PhantomData<T>);
unsafe impl<T: ?Sized> Sync for Invariant<T> {}
// Here, the `'_` is `'static`. As this compiles, the trait bound must be
// overriding the ambiguous type bounds and making the default
// trait object lifetime `'static` as well.
//
// When the trait bound is removed, an explicit lifetime is indeed required.
pub static OS: Invariant<Over<'_, dyn LifetimeTrait>> = Invariant(std::ptr::null_mut());
'static
trait bounds always override type bounds
As a special case of trait bounds overriding type bounds, a 'static
trait bound always applies.
Example
This example compiles. Playground.
use std::any::Any;
// n.b. `Any` has a `'static` trait bound
fn example(r: &dyn Any) {
let _r: &(dyn Any + 'static) = r;
}
This is true even if there are multiple lifetime bounds and one of them is 'static
(in contrast with the analogous case for type bounds, which remains ambiguous).
A single trait bound is not always the default
According to the reference, if the type has no lifetime bound and the trait has a single lifetime bound, the default trait object lifetime is the bound on the trait. This is true if the bound is 'static
, as covered above. However, it is not always true for a non-'static
bound.
(Again, when it is or isn't true -- when trait bounds "apply" or not -- is complicated).
Example
This example compiles. Playground.
trait Single<'a>: 'a {}
// `Box` has no type lifetime bounds, so according to the reference the
// default lifetime should be `'a`. But instead it is `'static`.
fn foo<'a>(s: Box<dyn Single<'a>>) {
let s: Box<dyn Single<'a> + 'static> = s;
}
The default depends on lifetimes being early or late bound
According to the reference, the default lifetime only depends on the presence or absence of bounds on type and traits. However, the presence or absence of bounds on function lifetime parameters also plays a role. Namely, early-bound lifetime parameters (lifetimes involved in a where clause) act differently than late-bound lifetime parameters.
Example
This example does not compile. Playground. However, the only change from the last is making the lifetime parameter 'a
early-bound.
trait Single<'a>: 'a {}
// Unlike the last example, the trait bound now applies and the
// default trait object lifetime is `'a`, causing a compiler error.
fn foo<'a: 'a>(s: Box<dyn Single<'a>>) {
let s: Box<dyn Single<'a> + 'static> = s;
}
This behavior has been known about for some time. Note that the linked issue was cited when the current reference material was written; I don't know why the information wasn't included in the reference at that time.
The default lifetime can override the wildcard '_
notation
According to the reference, using '_
in place of elision restores "the usual elision rules". However, this is not always true: trait bounds override both type bounds and the wildcard '_
lifetime in function bodies.
Examples
This example does not compile. Playground.
trait Single<'a>: 'a {}
fn foo<'a>(bx: Box<dyn Single<'a> + 'static>) {
// The trait bound applies and the default lifetime is `'a`
let bx: Box<dyn Single<'a> + '_> = bx;
// So this fails
let _: Box<dyn Single<'a> + 'static> = bx;
}
This example does not compile. Playground.
trait Single<'a>: 'a {}
fn foo<'a>(rf: &(dyn Single<'a> + 'static)) {
// The trait bound applies and the default lifetime is `'a`
let a: &(dyn Single<'a> + '_) = rf;
// So this succeeds
let _: &(dyn Single<'a> + 'a) = a;
// And this fails
let _: &(dyn Single<'a> + 'static) = a;
}
This example does not compile. Playground. The only difference from the last example is making the reference lifetime explicit. (As was noted above, this changes the default lifetime when there is no trait bound; this demonstrates that the trait bound still overrides the type bound in this scenario.)
trait Single<'a>: 'a {}
fn foo<'r, 'a>(rf: &'r (dyn Single<'a> + 'static)) {
// The trait bound applies and the default lifetime is `'a`
let a: &'r (dyn Single<'a> + '_) = rf;
// So this succeeds
let _: &'r (dyn Single<'a> + 'a) = a;
// And this fails
let _: &'r (dyn Single<'a> + 'static) = a;
}
Other underdocumented behavior of note
I don't think the current reference material contradicts these observations, but it doesn't point them out either.
Trait bounds introduce implied bounds on the trait object lifetime
Similar to how &'a &'b ()
introduces an implied 'b: 'a
bound, trait Trait<'b>: 'b
introduces an implied 'b: 'a
bound on dyn Trait<'b> + 'a
. (The bound is always present, e.g. even if the trait object lifetime is elided.)
Examples
This example compiles. Playground.
pub trait LifetimeTrait<'a, 'b>: 'a {}
fn fp<'a, 'b, 'c>(t: Box<dyn LifetimeTrait<'a, 'b> + 'c>) {
// This compiles which indicates an implied `'c: 'a` bound
let c: &'c [()] = &[];
let _: &'a [()] = c;
}
This example does not compile. Playground.
pub trait LifetimeTrait<'a, 'b>: 'a {}
pub fn f<'b>(_: Box<dyn LifetimeTrait<'_, 'b> + 'b>) {}
fn fp<'a, 'b, 'c>(t: Box<dyn LifetimeTrait<'a, 'b> + 'c>) {
let c: &'c [()] = &[];
// This fails, demonstrating that `'c: 'b` is not implied
// (i.e. the implied bound is on the trait object lifetime only, and
// not on the other parameters.)
let _: &'b [()] = c;
// This fails as it requires `'c: 'b` and `'b: 'a`
f(t);
}
Type bounds of aliases take precedence
That's to say, if you have an alias without the lifetime bound, it will act like Box<T>
(default is often 'static
) even if the underlying type had the bound. And if you have an alias with the lifetime bound, it will act like &T
(default is often the bound lifetime) even if the underlying type did not have the bound.
Examples
This example compiles. Playground.
trait Trait {}
// Without the `T: 'a` bound, the default trait object lifetime
// for this alias is `'static`
type MyRef<'a, T> = &'a T;
// So this compiles
fn foo(mr: MyRef<'_, dyn Trait>) -> &(dyn Trait + 'static) {
mr
}
This example does not compile. Playground.
trait Trait {}
// Without the `T: 'a` bound, the default trait object lifetime
// for this alias is `'static`
type MyRef<'a, T> = &'a T;
// With the `T: 'a` bound, the default trait object lifetime for
// this alias is the lifetime parameter
type MyOtherRef<'a, T: 'a> = MyRef<'a, T>;
// So this does not compile
fn bar(mr: MyOtherRef<'_, dyn Trait>) -> &(dyn Trait + 'static) {
mr
}
Bounds on associated types and GATs don't change the default
This is true when the bounds are placed on the associated type or GAT itself.
Example
This example does not compile. Playground.
trait Trait {}
impl Trait for () {}
trait BoundedAssoc<'x> {
type BA: 'x + ?Sized;
}
// Still `dyn Trait + 'static`
impl<'x> BoundedAssoc<'x> for () {
type BA = dyn Trait;
}
// Fails as `'a` might not be `'static`
fn bib1<'a>(obj: Box<dyn Trait + 'a>) {
let obj: Box< <() as BoundedAssoc<'a>>::BA > = obj;
}
trait BoundedAssocGat {
type BA<'x>: 'x + ?Sized;
}
// Still `dyn Trait + 'static`
impl BoundedAssocGat for () {
type BA<'x> = dyn Trait;
}
// Fails as `'a` might not be `'static`
fn bib2<'a>(obj: Box<dyn Trait + 'a>) {
let obj: Box< <() as BoundedAssocGat>::BA::<'a> > = obj;
}
However, it is also true for GATs with bound type parameters.
Example
This example does not compile. Playground.
trait Outer {
type Ty<'a, T: ?Sized + 'a>;
}
impl Outer for () {
type Ty<'a, T: ?Sized + 'a> = &'a T;
}
trait Inner {}
// The parameter resolves to `&'r (dyn Inner + 'static)`
fn g<'r>(_: <() as Outer>::Ty<'r, dyn Inner>) {}
// So this fails
fn f<'r>(x: <() as Outer>::Ty<'r, dyn Inner + 'r>) { g(x) }
This latter concern is tracked in #115379 (which is also where the example is from).
Overview of the actual behavior
Type bounds always apply if there are no trait bounds, but also in other circumstances we'll cover below.
When type bounds apply:
- The wildcard
'_
always restores the "normal" elision rules - If there are 0 lifetime bounds
- The default is inferred in expressions
- The default is
'static
everywhere else - Example:
Box<T>
- If there is 1 lifetime bound,
'a
- The default is usually inferred in expressions
- Exception: If
'a
is explicitly annotated in an expression, the default is'a
- Exception: If
- The default is
'a
everywhere else - Examples:
&T
,Ref<'a, T>
- The default is usually inferred in expressions
- If there are multiple lifetime bounds (even if one of them is
'static
)- The default is inferred in expressions
- The default is considered ambiguous everywhere else and must be explicitly annotated instead
When there are trait bounds, the bounds always implicitly apply to the trait object lifetime, whether that bound is elided, the wildcard '_
, or explicitly annotated. In particular, if the trait has a 'static
bound, the trait object lifetime is effectively always 'static
. This is considered unambiguous even if there are other lifetime bounds (in contrast with type bounds).
Therefore, when a trait has a 'static
bound, irregardless of anything else
- The default is effectively
'static
(even when technically inferred or another lifetime parameter)
From here on, we assume any trait bounds are non-'static
.
When there are trait bounds, they usually apply fully. The exception is function signatures, which we'll cover separately.
When trait bounds are present and apply fully:
- Type bounds do not apply
- The wildcard
'_
usually restores the "normal" elision rules- Exception: In expressions,
'_
acts like full elision - The trait bounds still imply bounds on the anonymous lifetime
- Exception: In expressions,
- If there is 1 lifetime bound,
'a
- The default lifetime is
'a
- The default lifetime is
- If there are multiple lifetime bounds
- The default is considered ambiguous and must be explicitly annotated instead
In function signatures when trait bounds are present, trait bounds may apply fully, partially, or not at all (falling back to type bounds). I've written up the detailed behavior in #47078; in summary:
- If any bounding parameter is explicitly
'static
, the default lifetime is'static
- If exactly one bounding parameter is early-bound, the default lifetime is that lifetime
- Including if it is in multiple positions, such as
dyn Double<'a, 'a>
for traitDouble<'a, b>: 'a + 'b
- Including if it is in multiple positions, such as
- If more than one bounding parameter is early-bound, the default lifetime is ambiguous
- If no bounding parameters are early-bound, the type bounds apply instead
When trait bounds partially apply, interaction with the inferred bounds from the trait (which are always in effect) can create surprising behavior, as explored in the linked issue.
Other behavior of note:
- Bounds or lack of bounds on type aliases take precedence
- Bounds on associated types and GATs have no effect
- Bounds on GAT type parameters have no effect