Why 'a must outlive 'static here?

The code fails to compile:

fn main() {
    trait Tr<'a, 'b> {}
    impl<'a> Tr<'a, 'static> for &'a i32 {}

    fn a<'a>(n: &'a i32) -> Box<dyn Tr + 'a> {
        Box::new(n)
    }
}

reporting:

error: lifetime may not live long enough
   --> src/main.rs:161:9
    |
160 |     fn a<'a>(n: &'a i32) -> Box<dyn Tr + 'a> {
    |          -- lifetime `'a` defined here
161 |         Box::new(n)
    |         ^^^^^^^^^^^ returning this value requires that `'a` must outlive `'static`

error: could not compile `rust-test` due to previous error

I wander why it is necessary for 'a : 'static.

To my understanding, Tr<'a, 'static> means some type, say T, must satisfies both 'a : 'T and 'static : 'T, effectively 'a : T is enough because 'static : 'a for any 'a.

dyn Tr borrows T, so T outlives dyn Tr is required, Box<dyn Tr + 'a> is enough for soundness.

I do know the rule of Lifetime elision - The Rust Reference

If the trait object is used as a type argument of a generic type then the containing type is first used to try to infer a bound.

  • If there is a unique bound from the containing type then that is the default
  • If there is more than one bound from the containing type then an explicit bound must be specified

If neither of those rules apply, then the bounds on the trait are used:

  • If the trait is defined with a single lifetime bound then that bound is used.
  • If 'static is used for any lifetime bound then 'static is used.
  • If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is 'static outside of expressions.

And the above code hits If 'static is used for any lifetime bound then 'static is used.
In fact, to verify the rule is why the code was written.

Well, I still don't know why the rule is reasonable,

This

    fn a<'a>(n: &'a i32) -> Box<dyn Tr + 'a> {

Is short for this

    fn a<'a>(n: &'a i32) -> Box<dyn Tr<'_, '_> + 'a> {

Is short for this

    fn a<'a>(n: &'a i32) -> Box<dyn Tr<'a, 'a> + 'a> {

But Tr<'x, 'y> is only implemented for &'a i32 when 'x == 'a and 'y == 'static, so this

        Box::new(n)

Can only produce a Box<dyn Tr<'a, 'a>> when 'a == 'static.

In contrast, this compiles.


I don't think it mattered here, but also note that trait parameters are invariant.

I recommend using #![deny(elided_lifetimes_in_paths)] to make lifetime relationships less obscured.

6 Likes

I don't know what you mean here because 'lifetime: T has no meaning in Rust. And given a type T, there is no 'T defined either. Morever T is coming out of nowhere; do you mean an implementor of Tr<'a, 'static>? Because there is also no required relationship between the implementor's validity and the parameters of the trait.

why the two equals.

Shouldn't impl<'a> Tr<'a, 'static> for &'a i32 {} require Box<dyn Tr<'_, '_> + 'a> be inffered to Box<dyn Tr<'a, 'static> + 'a>?

Anyway, the only way compiler taking a &i32 as a Tr is to force the second type parameter be 'static, and the first to be 'a.

There is no

impl<'a, 'b> Tr<'b, 'b> for &'a i32 {}`

As per lifetime elision of functions. The dyn Tr has nothing to do with it.

Perhaps you're confusing the trait object lifetime with parameters of the trait. They are two different things. The type parameters of trait follow function lifetime elision and default to 'a. They do not default to 'static, even inside of a Box.

I use 'lifetime: T to express T can not outlive lifetime

My understanding the relationship of the two by comparing it to a struct:

 struct tr<'a, 'b> {
     a: &'a i32,
     b: &'b i32,
 }

A tr<'c, 'd> can't outlive min('c, 'd) otherwise a dangle will happen.

Well, maybe it is not the reality and I should not deduce based that

Yes. And the compiler did infer that

fn a<'a, 'b>(n: &'a i32) -> Box<dyn Tr<'_, 'b> + 'a> {

5 |     fn a<'a, 'b>(n: &'a i32) -> Box<dyn Tr<'_, 'b> + 'a> {
  |              -- lifetime `'b` defined here
6 |         Box::new(n)
  |         ^^^^^^^^^^^ returning this value requires that `'b` must outlive `'static`

Rust's elision rules make the funtion with the contract

fn a<'a>(n: &'a i32) -> Box<dyn Tr<'a, 'a> + 'a> {

and it's conflicting with code inside.

So it's your duty to write the correct contract on the function instead of letting rustc choose for you. Rust can't infer the contract. Do read


Please don't use the confusing notation that doesn't exist in Rust. It's not just once to see you use it for granted.

3 Likes

If T: Sized + Trait<...> + 'dyn, and if the trait is object safe, T can coerce to dyn Trait<...> + 'dyn. The parameters in ... don't limit or influence 'dyn.

For example.


Let's say struct S<'a, 'b>: Trait + Sized. Then can S<'a, 'b> can coerce to

dyn Trait + min('a, 'b)

(also known as the least upper bound, or LUB).

If neither 'a or 'b is constrained, you can't coerce to either of

dyn Trait + 'a
dyn Trait + 'b

since without constraints you don't know the LUB.

Example.

If you don't want to constrain 'a: 'b or 'b: 'a, you can add constraints for the LUB like so:

fn foo<'a: 'c, 'b: 'c, 'c>(tuple: (&'a str, &'b str)) -> Box<dyn Trait + 'c> {
    Box::new(tuple)
}

'c is at most the LUB of 'a and 'b.


These bounds can be satisfied:

dyn Tr<'a, String> + 'static: 'a
dyn Tr<'static, String> + 'a: 'a

These bounds cannot be satisfied:

dyn Tr<'a, String> + 'static: 'static
dyn Tr<'static, String> + 'a: 'static

The relationship is defined syntactically, and to satisfy

dyn Tr<'x, Y> + 'z: 'bound

You need to satisfy all of

'x: 'bound
 Y: 'bound
'z: 'bound
2 Likes