What value does the lifetime parameter infer if no reference is passed in

trait MyTrait<T>{
    
}
impl<T> MyTrait<T> for i32{}

fn show<'a>(x:Box<dyn MyTrait<&'a i32>>){}
fn main(){
    let i = Box::new(0);
    show(i);
}

The value of the lifetime parameter in the trait object is confusing. No argument that is passed in is involved reference. I wonder, in this case, which entity for which the lifetime parameter is inferred?

show declares it can handle any lifetime 'a, so I don't think it really matters in this case -- as there's nothing outside the call (such as an output value) to influence the inferred lifetime. No matter what lifetime is inferred, there's no trait bound violation or change in behavior, so :man_shrugging:.

More generally, I think the compiler tends to pick the shortest lifetime as it tends to be the most flexible, but (a) I don't think that's guaranteed per se, and (b) again I don't think it matters in this case.

1 Like

Actually, this question is another part of Can the lifetime parameter in dyn trait object not be covariance?

In that case, we say the reference whose lifetime is 'a cannot be used to take a local variable defined in the function body. But, you answered that

show declares it can handle any lifetime 'a

So, this is confusing. Should we say that 'a can be any lifetime the caller can specify that is longer than any lifetime in the function body?

That part is true. Is the confusing part that I said "any lifetime"? Or is there another question?

"any lifetime" is confusing here, in the linked question, the lifetime cannot be shortened to be compatible with the local variable. The key point here is, there is no reference/borrow involved here such that the value of the lifetime parameter is a bit obscure. We seem to agree that the value of the lifetime parameter can be any lifetime except that it needs to be longer than that in the function body.

@quinedot Another question is what is the default lifetime trait bound for the trait object here? Is it 'static( Box<dyn MyTrait<&'a i32> + 'static>) or something else(Box<dyn MyTrait<&'a i32> + '?>)?

Heads-up: I link to a few RFCs below that were written before dyn Trait required dyn, so you have to mentally track when they're talking about Trait as a trait or Trait as the type we call dyn Trait today.


The default here:

fn show<'a>(x:Box<dyn MyTrait<&'a i32>>){}

Is 'static, and thus 'static is used for the function declaration. You can't call it with something shorter. It's 'static because

Whereas here:

    let i = Box::new(0);

There is no default per se; instead, the behavior is to infer the value from context. In this example, it is inferred to be 'static as that's the only lifetime that can work given the declaration above.

But within a function body such as this, because the default behavior is to be inferred, it doesn't have to be 'static.

So you can't just look at a Box<dyn Trait> and know if it's + 'static or not; you have to know what context you're in. And if you're in an inferred context, it still may not be obvious.


The default lifetime of dyn Trait<&'a SomeType> is never 'a unless there is something else in the environment involving 'a though; defaults aren't just "pulled up" from T when T happens to resolve to a type with a lifetime.

I had to phrase that carefully as perhaps you have a &'a dyn Trait<&'a SomeType>, or perhaps you're in a struct definition that declares T: 'a. In those cases, the default is 'a. Yes, there are a lot of rules and it's sort of a mess.


  1. adjusted by RFC 1156 ↩︎

2 Likes

Thanks. I read the RFC about the default lifetime trait bound in a trait object, however, the paper is not easy to understand(the meaning of many symbols is ambiguous). Could you please conclude what the default lifetime trait bound will be in a simple way?

You mean, summarize all the cases? I wouldn't really call it simple, but I can give it a shot. I'll check myself so it'll take a minute.

Yes.

but I can give it a shot. I'll check myself so it'll take a minute.

Appreciate for doing that.

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.

4 Likes

Frankly, It's too complex for me. I cannot understand it entirely, anyhow I consider it a good answer. I will try to understand this answer If I will have more bits of knowledge about this part.

My preliminary understanding of the lifetime trait bound of a trait object is that it is a record of the information of whether a type T satisfies T: 'lifetime before the type erasing(i.e &T coerced to & dyn Trait). The default lifetime trait bound is that the compiler automatically marks what the lifetime should be in the trait object in order to avoid redundantly user specify.

I feel most people, even experts, don't know all the ins and outs of what trait lifetime defaults show up where. I feel I understand them pretty well and I hit a couple surprises writing that up. So I wouldn't sweat the details too much; a rough estimate of "'static for Box, not-'static for & [mut]" will get you a long way. "Or inferred in a function body" fills in most the rest.

Your preliminary understanding of the lifetime itself is good.

1 Like

Landed in 1.16. There was no FCP.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.