Let's use this description as a starting point. The main clarifications or differences I have found are:
-
&'a T
and &'a mut T
have T: 'a
-like behavior
-
static
and const
declarations override the default with 'static
as per RFC 1623
- But not when there are multiple lifetime bounds
- The innermost wrapping type determines the default
- So it's
'static
for &'a Box<dyn Trait>
- The default cannot see through / override aliases, including
Self
- So if you have
Self = dyn Trait
, the default applies to the alias and Self
is dyn Trait + 'static
- Bounds on type aliases influence the default (!)
- So if you have a
type Alias<'a, T: 'a + ?Sized> = Concrete<'a, T>
, the default will be 'a
for Alias<'a, dyn Trait>
even though it's 'static
for Concrete<'a, dyn Trait>
- Bounds on associated types and GATs do not influence the default
So, we need to talk about different type of default lifetime influencers, and different contexts. Influencers are typically generic type, but they can also be type aliases.
Influencer |
|
default |
struct NotBounded<'a, T> |
|
base-default |
Box<T>, Rc<T>, Arc<T>, ... |
|
base-default (not bounded) |
type NotBounded<'a> = ...; |
|
base-default (not bounded) |
struct Bounded<'a, T: 'a> |
|
'a |
&'a T, &'a mut T |
|
'a (bounded) |
type Bounded<'a, T: 'a> = ...; |
|
'a (bounded) |
struct OverBounded<'a, T: 'a + 'static> |
|
E0228 |
type OverBounded<'a, T: 'a + 'static> = ...; |
|
E0228 |
Note that the default based on the declaration of a type
alias takes precedence over the default of the aliased type. (If you put a bound on an alias, it will warn you that the bound won't be enforced, but it still changes the default object lifetime.)
Next we need to talk about contexts:
Context |
Behavior |
const and static declarations |
'static is the default lifetime, which overrides a default of 'a or the base-default, but does not override E0228 |
function bodies |
The base-default is an inference variable |
impl headers, associated types definitions, GAT definitions |
The base-default is 'static
|
However, one important note here is that the bounded case can also be an inference variable within a function body. So here:
// n.b.: (): Trait
fn gg() {
let local = ();
let obj: &dyn Trait = &local; // line 3
let _: &(dyn Trait + 'static) = obj; // line 4
}
The elided lifetime on the left of =
on line 3 is an inference variable, and the default trait lifetime is an independent inference variable. Thus on line 4, the outer lifetime can take on a lifetime shorter than the function body, while the trait lifetime can be inferred to be 'static
.
I have no citation for this behavior, but it's observable (though arguably counter to the RFCs).
Contrast this with
// n.b.: (): Trait
fn ff<'a>(u: &'a ()) {
let obj: &'a dyn Trait = u;
let _: &(dyn Trait + 'static) = obj;
}
This fails as the concrete lifetime 'a
(after monomorphization) causes the default trait lifetime to also be 'a
, and thus it cannot be inferred or coerced to be 'static
on the next line.
Changing the obj
ascription to &'a (dyn Trait + 'static)
allows it to compile.
This independence does not take place in positions where elision is a fresh generic (impl
headers, function input parameters).
Any place where the wildcard lifetime '_
is allowed, you can use it to replace the default. In function bodies that means an inference variable, in function inputs and impl
headers it means a fresh generic lifetime, in function outputs it means "use the elision rules". It's not allowed in the definition of associated types or GATs.
&(dyn Trait + '_)
&'a (dyn Trait + '_)
Box<dyn Trait + '_>
You can elide lifetimes of structs and traits as well, but as far as I know the elision always acts the same as '_
. (But I didn't check this.) There was a plan to deprecate such elision in favor of requiring '_
, but I don't know if it's going to be picked back up again (#![deny(elided_lifetimes_in_paths)]
).
Unsizing coercion can coerce the lifetime of applicability even when it is in an otherwise invariant position (RFC 0599 and see also this thread). This means you ususally don't need a &mut (dyn Trait + '_)
in function argument position, for example, because if you have a &'short mut (dyn Trait + 'static)
, it can coerce to a &'short mut (dyn Trait + 'short)
.
Summary
static
, const
Type |
Lifetime of applicability |
Box<dyn Trait> |
'static |
&'static dyn Trait |
'static |
&dyn Trait |
'static |
Box<dyn Trait + '_> |
'static |
&'static (dyn Trait + '_) |
'static |
&(dyn Trait + '_) |
'static |
impl
headers, fn
inputs
Type |
Lifetime of applicability |
Box<dyn Trait> |
'static |
&'a dyn Trait |
'a |
&dyn Trait |
Same generic as reference |
Box<dyn Trait + '_> |
Fresh generic |
&'a (dyn Trait + '_) |
Fresh generic |
&(dyn Trait + '_) |
Fresh generic |
fn
outputs
Type |
Lifetime of applicability |
Box<dyn Trait> |
'static |
&'a dyn Trait |
'a |
&dyn Trait |
Same as reference (i.e. elision rules) |
Box<dyn Trait + '_> |
Elision rules |
&'a (dyn Trait + '_) |
Elision rules |
&(dyn Trait + '_) |
Elision rules |
Associated Types, GATs
Type |
Lifetime of applicability |
Box<dyn Trait> |
'static |
&'a dyn Trait |
'a |
&dyn Trait |
E0637 |
Box<dyn Trait + '_> |
E0637 |
&'a (dyn Trait + '_) |
E0637 |
&(dyn Trait + '_) |
E0637 |
fn
bodies
Type |
Lifetime of applicability |
Box<dyn Trait> |
Inferred |
&'a dyn Trait |
'a |
&dyn Trait |
Inferred |
Box<dyn Trait + '_> |
Inferred |
&'a (dyn Trait + '_) |
Inferred |
&(dyn Trait + '_) |
Inferred |
Significant notes
-
struct NotBounded<'a, T>
acts like Box<_>
-
struct Bounded<'a, T: 'a>
and &'a mut _
act like &'a _
-
struct OverBounded<'a, T: 'a + 'b>
must be explicitly specified (E0228)
- Associated type and GAT bounds don't have an effect
-
type
alias bounds act like the above, and override the behavior of the aliased type
- More generally, defaults do not penetrate aliases (such as
Self
)
- The lifetime is covariant
- Unsizing coercion makes it covariant even in otherwise invariant positions
As you can see, there's a lot of nuance and detail, so it's possible I've missed something or not gotten everything correct.
Scratchpad.