#Lifetimes

32 messages · Page 1 of 1 (latest)

orchid vapor
#

The function has a lifetime 'a
This is not quite true.

The function "accepts" a generic lifetime 'a. When doing let y: &'a i32 = &x; you're saying "I assure you that x will live at least as long as 'a, so it's safe to make y point to it'.

That's wrong since the lifetime of x is scoped to the function while 'a is a generic lifetime.

PS: Not entirely sure what the compiler would put in 'a here though

orchid vapor
#

I have to admit I don't know rust well enough to tell you what 'a would be here.

My guess is that you could read this as "any lifetime that the compiler see as appropriate based on the context where and how this function is called" in this case. But this definition does not match with the lifetime of x which is dropped at the end of the function.

empty bay
#

what does it mean intuitively that 'a is a generic lifetime?
To my understanding: if you, for example, passed an i32 reference as one of the arguments, you could specify its lifetime explicitly (e.g. fn failed_borrow<'a>(value: &'a i32) ...). That syntax would mean that the lifetime 'a would be as long as the i32 reference would be valid for.

I might be overlooking something, but conceptually, the example you gave seems impossible: failed_borrow owns x, but you're trying to return a reference y to it, which is impossible, because owned variables are dropped at end of scope. To put it another way, you can't (to my knowledge) extend x's lifetime outside of the function it is in (unless you transfer ownership).

I must admit I'm not 100% sure, but if I had to provide a guess as to what region 'a covers in your example, it would probably be just the fourth line.

blazing abyss
#

'a is like a T its the caller's choice what to fill it in with

#

conceptually it could be any specific region of the program

#

the example code doesn't work because rustc can't be sure that x is always valid for any region the caller decides to pass in

#

now this one it can

#

?play ```rs
fn ok_borrow<'a>() {
let y: &'a i32 = &12;
}

untold zincBOT
blazing abyss
#

this is because 12 here is static promoted so lives for the entire program's life so is valid for all regions the caller could select

#

yes

#

the basic rule is when a value is const constructable (like a literal) and would otherwise be a temporary, it can instead be promoted to a static value

#

it's unspecified exactly what it would be as it's not connected to any values, I believe rustc chooses 'static in such cases

#

?play ```rs
fn failed_borrow<'a>(x: &'a str) {
let y: &'a i32 = &12;
}

fn main() {
let s = String::from("hello");

failed_borrow(&s);

}

untold zincBOT
blazing abyss
#

here 'a is conceptually the line failed_borrow(&s); and anything it calls

#

?play ```rs
fn failed_borrow<'a>(x: &'a str) {
let y: &'a i32 = &12;
}

fn main() {
let s = String::from("hello");

{
    let y = s.as_str();

    failed_borrow(y);
}

}

untold zincBOT
blazing abyss
#

here 'a is conceptually the the inner { ... } with y

#

(there is a subtlety here with variance i wont get into)

empty bay
#

I would explain it this way: specifying 'a on the function does not mean that 'a is the function's lifetime (there is no such thing). It's "some" lifetime, just like you would define a generic type that you can use inside the function. In your case, you then used 'a when defining y as a reference. Therefore I would say 'a is basically alive just on the fourth line.

Because x is not a constant (i.e. not 'static), the example does not make sense, since failed_borrow can't return a reference to a thing it owns, regardless of what lifetimes you specify. 'a doesn't "reach" outside the failed_borrow function in this case.

radiant tree
#

I can't think of very many cases where you'd want to do something like this. Effectively, what you're saying is "give me a lifetime and I'll construct a reference that's valid for it". In this example, that can't possibly be true - the function has to work with any possible lifetime, but if one's given that lives longer than x (which, realistically, it would, because 'a is specified by the caller and 'a will therefore continue to be valid after the function returns), then that reference is invalid, because you're saying it lives longer than it actually does.

Usually lifetime parameters show up when you're being given a reference to something, and you want to return a reference (or maybe do something with that reference) based on the one you're given. A super simple function is fn my_func<'a>(data: &'a MyStruct) -> &'a u32, which extracts some field from MyStruct (and we'll say that field has a type u32). You don't have to write the lifetime here at all, but it means the same thing even if you don't.

The caller will pass a reference to your function, and that reference will be used to figure out the value of 'a automatically. Then, because you know the data in data is valid for 'a, you can return a reference to one of its fields that's also valid for 'a.

radiant tree
#

It works in reverse: a reference valid for 'a will also be valid for the function, but a reference created in the function might not necessarily be valid for 'a.

#

If I have

let test = 1;
my_fn(&test);

Let's say that my_fn has a lifetime parameter 'b that's tied to the reference. For a reference to be valid for 'b, it needs to live as long as that &test reference lives for, which I believe is until test goes out of scope (although if I'm wrong, someone please correct me).

clear nimbus
#

&test isn't specifically involved at all

#

when a lifetime is used as a lifetime parameter to a function, there's an implicit requirement that that lifetime is valid for the entire function call frame

#

regardless of how that lifetime is used for other things

#

you can think of that as "the function's parameters must live long enough for it to use them"

#

and the function body can assume that the lifetime doesn't end before the function returns

#

and, therefore, the function body must assume that the lifetime parameter outlives all the locals inside the function

#

so the original

    let y: &'a i32 = &x;

is not valid because, no matter what specific lifetime 'a is, it must be one that outlives the function call frame. So, it cannot refer to things which are stored in the function call frame like x is.