What does T: 'a + 'b mean?

Does it mean T: min('a, 'b) indeed?

I think it means the union of 'a and 'b; that is, T must live for the entire duration of both 'a and 'b.

2 Likes

I think it is not correct.

T: 'a does not mean T outlives 'a;
It means any reference T held outlive 'a.

So, I guess T: 'a + 'b maybe mean any reference T held outlives 'a and outlives 'b.
Ok, I am wrong, due to this, T: 'a + 'b maybe mean T: max('a, 'b)

Just as T: Trait1 + Trait2 means T: Trait1 and T: Trait2, T: 'a + 'b means T: 'a and T: 'b. T must be valid for all of 'a and all of 'b.

So yes, it's like a union.

(There's no max as neither 'a nor 'b need be a subset of the other.)

7 Likes

If this is correct, Then T: 'static means T must valid for 'static, Ok

{
    let i = 9; // i : 'static
} // i drops here

Surely I is not valid for entire 'static.

Yes, the type i32 is valid for all of 'static (anywhere).

static I: i32 = 0;

That doesn't mean values of said type live for 'static.

See this misconception.

4 Likes

I think 'a and 'b is not a lifetime range by definition but the point at which the lifetime ends.
That means, when 'a and 'b start is not important, given they end at same point, then 'a : 'b && 'b : 'a.

This means max('a, 'b) can be used to express which one ends later.

Lifetimes don't have to be contiguous. But to be fair, it rarely matters in the context of lifetime bounds, for example on a function where both lifetimes are longer than your function body and you can't assume 'a: 'b or 'b: 'a unless those bounds are declared.

"max" is probably good enough casually.

Is it correct to think of a lifetime as a scope, and union of lifetime as union of scopes ?

I think a union of lifetimes should correspond to the intersection of scopes.

1 Like

It's not an intersection. It's a union.

The meaning of T: 'a is "the type T has no lifetime annotations shorter than 'a" or "values of type T will not contain dangling references if 'a has not yet ended".

So if T: 'a and T: 'b, then no lifetime annotations on T are shorter than 'a. Additionally, no lifetime annotations on T are shorter than 'b.

Or, equivalently, if 'a has not yet ended, then values of type T don't contain dangling references. Similarly, if 'b has not yet ended, then values of type T don't contain dangling references.

See the above. The type i32 has no lifetime annotations, so it doesn't have any lifetime annotations shorter than 'static. Similarly, the type i32 contains no references, so it will never contain a dangling reference anywhere in 'static.

2 Likes

Mhh, maybe my mental model of lifetimes and how it relates to scopes is wrong.

If we consider the following example:


(Playground)

then from my understanding, lifetime 'a refers to the blue scope and 'b to the red one. Now we construct a type Foo that holds references with those lifetimes. We call the function foo with this value which has the requirement that T: 'a + 'b.
My confusion now stems from the fact, that Foo(a_ref, b_ref) is only valid inside the blue red scope, as otherwise it would contain a dangling reference to b, but yet it satisfies 'a + 'b. This is why I thought the intersection of scopes would be a more correct analogy than union of scopes.

But maybe thinking of lifetimes as scopes is not a good model, especially given things like non lexical lifetimes? Or is the lifetime 'a automatically shortened to 'b when calling foo so the union of the shortened 'a and 'b actually corresponds to 'b?

Edit: Confused blue and red.

@RobinH A few different comments here.

  1. When you create a reference to a variable, the lifetime will correspond to the duration in which you are borrowing the variable, and not to the duration in which the variable exists. So the regions you have drawn are the maximum possible sizes of the lifetime annotations (since the variable cannot go out of scope while it is borrowed), but the lifetimes could be smaller than those boxes.
  2. The 'a and 'b lifetimes in foo are not necessarily the same as the ones in main, even if they have the same name. The call to foo will probably just choose 'a = 'b = duration_of_call_to_foo.
  3. It is indeed the case that the lifetime annotations on Foo can be shortened. Whenever you pass a Foo<'long, 'really_long> to a function, the compiler will automatically insert a conversion into Foo<'short, 'short> if it needs to do that to prove it correct.
1 Like

I suspected that as well. Would changing foo to fn foo<'a, 'b>(f: Foo<'a, 'b>) where Foo<'a, 'b>: 'a + 'b {} ensure that the (potentially shortened) lifetimes in main are used?

Thanks for the confirmation.

Hopefully this isn't too off-topic. I played around a little more, trying to actually get the lifetimes I drew in my previous post. And ended up with:

Code
use core::marker::PhantomData;

#[derive(Copy, Clone)]
struct Invariant<'a>(&'a i32, PhantomData<&'a mut &'a ()>);

impl<'a> Invariant<'a> {
    pub fn new(r: &'a i32) -> Self {
        Self(r, PhantomData)
    }
}

struct Foo<'a, 'b>(Invariant<'a>, Invariant<'b>);

fn foo<'a, 'b>(_f: Foo<'a, 'b>) where Foo<'a, 'b>: 'a + 'b {}

fn main() {
    {
        let a = 0;
        let a_ref /*: &'a i32 */ = Invariant::new(&a);
        
        {
            let b = 1;
            let b_ref /*: &'b i32 */ = Invariant::new(&b);
            
            foo(Foo(a_ref, b_ref));
        }
        println!("{}", a_ref.0);
    }
}

(Playground)

which does not compile with the Foo<'a, 'b>: 'a + 'b bound, but compiles if it is omitted. However, the error that is reported seems like a bug:

Error
error[E0597]: `b` does not live long enough
  --> src/main.rs:23:55
   |
22 |             let b = 1;
   |                 - binding `b` declared here
23 |             let b_ref /*: &'b i32 */ = Invariant::new(&b);
   |                                                       ^^ borrowed value does not live long enough
...
26 |         }
   |         - `b` dropped here while still borrowed
27 |         println!("{}", a_ref.0);
   |                        ------- borrow later used here

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

Should I create an issue for this or am I missing something and this is expected?

I personally find that conflating scopes and lifetimes leads to more confusion that elucidation (especially within a function body). When talking about bounds on a function, one can usually be a lot more loose, because the only information besides "longer than the function call" come in the form of bounds (explicit or inferred), and covariance is more common than invariance.

In your image, by the time b_ref is created, 'a and 'b have the same lifetime. At that point, both of their last usage is on the same line (and nothing else that interacts with the lifetime happens between).

If you want 'a to be longer, add a use of a_ref after the inner block, say.


If they're shortened, how are they the ones in main? Are you thinking the call will make the compiler choose shorter lifetimes in main? Generally, you can think of inferred lifetimes in the function body as being as short as possible so that everything works for all the uses, so adding uses isn't going to shorten them.

If you want to prevent the lifetimes used in the call being shorter than they are in main, you need to make the lifetimes invariant.


Let's look at what this bound means:

Foo<'a, 'b>: 'a + 'b

For a generic struct to be valid, it's parameters must be valid. So Foo<'a, 'b> is only valid where 'a and 'b are valid. That would be the intersection of 'a and 'b. But 'a + 'b is the union. So we're saying the intersection must contain the union. That only way that can happen is if the intersection is the union; that is, if 'a == 'b.

Or we can take it piecemeal. For Foo<'a, 'b>: 'a + 'b to hold, these must also hold:

  • 'a: 'a + 'b
    • 'a: 'a
    • 'a: 'b
  • 'b: 'a + 'b
    • 'b: 'a
    • 'b: 'b

The presence of 'a: 'b and 'b: 'a means that 'a == 'b.

So your bound is just a fancy way of saying 'a == 'b.


Now let's put that all together by looking at your playground. You have

  • Added a use of a_ref after the block so 'a is definitely larger than 'b
  • Changed Foo so that the lifetimes are invariant
  • Added your Foo<'a, 'b>: 'a + 'b bound that means 'a == 'b.

And the borrow error is because 'b cannot be forced to be as long as 'a.

If you remove the println, it compiles, because the lifetimes can be the same.

If you rewrite foo to take Foo<'a, 'a> with no bound (i.e. another way of saying the two lifetimes must be the same), you get the same behavior.


The error is correct in the sense that you've created some unsatisfiable constraints: 'b must be active where 'a is used, but 'b cannot be longer than the inner block because it borrows something local to that block.

The error message is confusing in that it talks about "using (a borrow of b)". When lifetimes become intertwined, the borrows involved can "see" each other, and the use of one is considered a use of the other, in some sense. Why? If you had actually passed a &mut a_ref and &mut b_ref with these bounds, for example, you could have swapped a_ref and b_ref in foo, and there could be a use of the borrow of b at the println.

That can't happen here sense you only actually passed some copy of your references, but the error reporting hasn't dug deep enough to know that (and I'm not sure how feasible it would be to do so).

So there's a diagnostic bug in that conveying why there's an error could be improved, but it is correct that the program has unsatisfiable constraints.

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.