Lifetime bounds in Error code E0700

https://doc.rust-lang.org/error_codes/E0700.html has an example:

use std::cell::Cell;

trait Trait<'a> { }

impl<'a,'b> Trait<'b> for Cell<&'a u32> { }

fn foo<'x, 'y>(x: Cell<&'x u32>) -> impl Trait<'y> + 'x // why + 'x?
where 'x: 'y
{
    x
}

This code compiles but I don't understand the lifetime bound + 'x. Here the return value is a Cell containing a reference with 'x lifetime. So the Cell shall not live longer than 'x, which is 'x: Cell. But + 'x in return position is saying Cell: 'x. Isn't this the opposite? I don't see why the return value must outlive 'x.

(I'm ignoring where 'x: 'y because the code compiles without it.)

What this requires is Cell<&'x u32>: 'x. In general (at least as of now) T: 'a just means that all lifetimes 'b that are syntactically mentioned in T must fulfill 'b: 'a.

(And, if other generics are still present, all type parameters S syntactically mentioned in T must fulfill S: 'a, so that later, when S is instantiated, the lifetimes that might be mentioned in it, too, can be restricted in the same manner.)

For the concrete case, Cell<&'x u32>: 'x is thus boiling down to 'x: 'x (“'x outlives 'x”) which is trivially true, since the “outlives” relation of lifetimes is one of “larger or equal” kind, i.e. it’s reflexive, and every lifetime fulfills the outlives relation with itself, 'a: 'a.

The addition of + 'x to the impl Trait… however has a different main effect for fixing the error. There is a special rule for impl Trait-types that (all generic type parameters and) all lifetimes that are mentioned in the trait bound become “part of” (think: parameters of) the opaque type. Lifetimes that aren’t mentioned are thus like lifetime that aren’t parameters of a struct, e.g. if you define a struct

struct Foo {
    field: &'a u8,
}

fn foo<'a>(x: &'a u8) -> Foo { Foo { field: x } }

the compiler will complain that 'a is not a parameter of Foo - you’d need to write

struct Foo<'a> {
    field: &'a u8,
}

fn foo<'a>(x: &'a u8) -> Foo<'a> { Foo { field: x } }

instead. It’s a “dumb” syntactical thing - it’s also a relatively arbitrary rule that impl Trait works this way - there’s even ongoing discussions (in any case, this RFC text also provides a write-up of the relevant context) about whether and how this rule should perhaps change in the next edition :wink:

Are you saying Cell<&'x u32>: 'x imposes no bounds on the lifetime of Cell itself but only the references contained within?

Where can I find the syntax T: 'a in the Rust Reference? I probably misunderstood what this means. I thought this means the lifetime of T itself is bounded by 'a. Then requiring all references T contains to be bounded by 'a is a natural follow-up because such references must outlive T itself.

Lifetime bounds

Lifetime bounds can be applied to types or to other lifetimes. The bound 'a: 'b is usually read as 'a outlives 'b. 'a: 'b means that 'a lasts at least as long as 'b, so a reference &'a () is valid whenever &'b () is valid.

fn f<'a, 'b>(x: &'a i32, mut y: &'b i32) where 'a: 'b {
    y = x;                      // &'a i32 is a subtype of &'b i32 because 'a: 'b
    let r: &'b &'a i32 = &&0;   // &'b &'a i32 is well formed because 'a: 'b
}

T: 'a means that all lifetime parameters of T outlive 'a. For example, if 'a is an unconstrained lifetime parameter, then i32: 'static and &'static str: 'a are satisfied, but Vec<&'a ()>: 'static is not.

Yes, exactly. The “Cell” on its own has no notion of “lifetime” that is tracked by the borrow checker. (If the Cell itself was, again, borrowed, as in &'y Cell<&'x u32>, then of course, it’s tracked again, as then the lifetime 'y must not exceed the time the Cell lives.)

Seel also 2) and 3) in “Common Rust Lifetime Misconceptions” by pretzelhammer.

1 Like

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.