#Lifetime declaration in functions and structs

9 messages · Page 1 of 1 (latest)

plain zinc
#

Hi, I'm trying to understand lifetimes better, and one thing I still don't fully understand is lifetime declaration at functions and structs.

I roughly understand that for functions, lifetimes describe a relation between the arguments passed to the function and the return value, and subtyping and variance is used to coerce all the lifetimes within a function body to the same value, which is then used down the line by the compiler. For instance:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

I get that in this code, the input lifetimes as well as the output lifetime are coerced to the same lifetime through subtypes, so if x: &'a str and y: &'b str is passed into the function where 'a is a subtype of 'b, 'a is coerced to 'b and the return value must have a lifetime of 'b or a subtype of 'b.

However, in Rust By Example, the comment says that the reference must live as long as the function:

// One input reference with lifetime `'a` which must live
// at least as long as the function.
fn print_one<'a>(x: &'a i32) {
    println!("`print_one`: x is {}", x);
}

It says the same thing for structs:

// A type `Borrowed` which houses a reference to an
// `i32`. The reference to `i32` must outlive `Borrowed`.
#[derive(Debug)]
struct Borrowed<'a>(&'a i32);

Question: Does the generic lifetime declaration in the function and struct definition imply some kind of relation between the lifetime of the function and struct, or does the compiler simply check that the lifetime of the arguments and values in the struct must be longer than the lifetime of the function and struct? i.e. Does the lifetime of the function and struct play any role in coercion of lifetimes, or is there just a simple verification to check that the coerced lifetime is longer than the function/struct lifetime?

#

Mental Model: I have two mental models related to this:

  1. The lifetime relation for the arguments is calculated independently of the function / struct lifetime, then compared against the function / struct lifetime to ensure that it is longer than the function / struct lifetime.
  2. The generic declaration of the lifetime in the function or struct simply places an upper bound on how long the function or struct can last relative to the arguments, with the lifetime relation of the arguments calculated first.

Are either of these correct, or is there a more accurate way to see declaration of lifetimes in functions / structs?

signal heath
#

First of all, I think it's useful to avoid the idea of "the lifetime of" anything; some things outlive or are outlived by, and some things have lifetime parameters, but speaking of "the lifetime of" too readily conflates different things.

#

As to your specific question, I think model 1 is closer to correct.

#

Borrow checking / lifetime checking needs to be understood as a constraint satisfaction problem; the compiler builds a list of of outlives requirements, both explicit and implicit, then checks if it's possible for them all to be true at once. If so, your program compiles; if not, then error.

#

When you write fn longest<'a>( this is introducing a lifetime-variable which you can then use to discuss lifetime relationships between the parameters and return value, but it also comes with a constraint that 'a outlives the (particular) execution of the function body.

#

There's a sense in which it has to; the caller of the function can specify that lifetime (remember, it is a kind of parameter), and they have no way of making any finer distinctions than "live when calling the function" or "not live when calling the function".

#

With structs it's a little more clear that you need that constraint: it would be unsound for a struct to be used while it contains some value whose lifetime has expired, because it might access a dangling reference.

#

So, just like you can't have any uses of a &'a str after the end of 'a, you also can't have any uses of a Foo<'a> after the end of 'a, regardless of what Foo is.