Explain this program. just for understanding purpose

#![allow(warnings)]

fn ok<'lt>(mut e: &'lt u32) {
   
    let x = 4;
    e = &x;
}

fn main() {
    let mut z = 0;
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `x` does not live long enough
 --> src/main.rs:6:9
  |
3 | fn ok<'lt>(mut e: &'lt u32) {
  |       --- lifetime `'lt` defined here
4 |    
5 |     let x = 4;
  |         - binding `x` declared here
6 |     e = &x;
  |     ----^^
  |     |   |
  |     |   borrowed value does not live long enough
  |     assignment requires that `x` is borrowed for `'lt`
7 | }
  | - `x` dropped here while still borrowed

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

Error says borrowed value doesn't live long enough

But here

e parameter and x variable has at same scope right

Then why error happened?

the lifetime parameter 'lt of the function ok represents a region outside the function, determined by the caller, but the variable x only lives inside the function.

3 Likes

can u explain with some more detailed way

for any function in rust, there's a difference between named lifetime parameters and local lifetimes.

from the perspective of the function, any named lifetime parameters must live as long as the entire function (and beyond), particularly, it does NOT end at the closing curly brace of the function body.

but any borrows of local variables within the function body must end before the function returns, i.e. right before (or at, depending on how you imagine it) the closing brace.

so, in your example, the lifetime of the borrow &x is "shorter" than the lifetime 'lt, so you cannot assign &x to e, and the borrow checker reports this violation.

2 Likes

I suggest not thinking of lifetimes in terms of scopes or vice-versa. Instead think of lifetimes the duration of some borrow.[1] And think of going out of scope as using the variable in a certain way.[2]

It is an error for a variable to go out of scope (be "dropped") when borrowed. It is also an error to move or take a &mut to a variable that is borrowed. They are all uses of a variable that are incompatible with being borrowed.

In combination with @nerditation's explanation of how named lifetimes correspond to borrows longer than the function, I hope this makes the error message more clear.


  1. This is also an approximation, as some other threads of yours demonstrate. But it is significantly closer to how things actually work. ↩︎

  2. It runs the destructor, if there is one, and makes the variable uninitialized. ↩︎

1 Like

How about an analogy...

Imagine I give you a piece of paper on which to write down somebodies phone number. Call the piece of paper "e". Call the phone number "x" which happens to be 4.

Your friends phone number is 4 and you write that on the piece of paper.

You give that piece of paper back to me. Now I have the phone number "x" which is 4.

Problem is by the time I get that piece of paper (e) back that somebody (x) has cancelled their phone subscription. The number I have on the piece of paper is no longer valid. Actually it's worse than that, that somebody (x) has died already. They no longer exist. They died the moment the ok() function returned.

If I were able to use the phone number you wrote on the paper I would get an out of service error or perhaps reach the wrong person.

I know, all apologies are terrible, but how about that.

1 Like

I suspect the thing that confuses people is that lifetimes are a property of objects but the annotations go on the references.

Objects are created and destroyed at some points in time. Be it local variables created on the stack, which go out of scope when the function ends, or on the heap, where they can live much longer. Those object lifetimes are a result of where and how you have created them, the structure of your program. Lifetime annotations do nothing to alter that.

Meanwhile lifetime annotations on references only say "I need to refer to a thing that is going to live at least as long as some other thing or at least as long as I exist myself".

I think the term "borrow duration" suggested by quinedot and others helps indeed to reduce confusion. I tried to use this term in my book since early summer now, see Lifetimes of references - Rust for C-Programmers

Interesting.

It seems to me that people coming to Rust from Javascript, Python etc first need to get the new (to them) notion that objects disappear, that objects have life times. There is no magic in the background keeping things alive for as long as you can use them.

Then we come to references and borrows and those dread tick marks. Perhaps "borrow duration" is a more apt name for them, given that we talk of the "borrow checker", "mutable borrows", "immutable borrows" etc.

Using the same word for both concepts is definitely a source of much confusion. So much so that I do my damnedest to not refer to the value dropping concept as a lifetime at all, when discussing Rust.

(I agree it would be better if we called Rust lifetimes -- '_ things -- as something else, "regions" or "durations" or whatever, instead. But it seems unlikely we'll successfully change the Rust terminology everywhere at this point.)

When you have <'lt> on definition of a function, struct, or a trait, it always refers to a lifetime that is longer and exceeds the inner scope of the thing having <'lt>. So it will never be sufficient for variables living inside the function, and in structs it will never be sufficient for struct's own fields, and in trait it will never be sufficient for the object implementing the trait. It's defines something "over there".

Second thing to remember is that lifetimes are checks and restrictions, not commands. Rust will never do anything to make data live long enough. It only uses lifetimes to abort compilation when the program doesn't already have data living long enough.

1 Like