How to understand lifetime

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";
    let result = longest(string1.as_str(),string2);
    println!("The longest string is {}",result);
}
fn longest<'a>(x:&'a str,y:&'a str) -> &'a str{
    if x.len() > y.len(){
        x
    }else{
        y
    }
}

In the above example, is the lifetime of 'a equal to the lifetime of 'string2'?Are lifetime and scopes the same?
the string slice returned from the function will live at least as long as lifetime 'a,so,the lifetime of 'result' is also equal to 'a,but 'result' has a smaller scope than 'string2',Doesn't that contradict the fact that they both have a lifetime of 'a

No. Lifetimes can begin and end at different points than scopes begin and end; and in fact they aren't even required to be contiguous.

No. The string referenced by string2 lives for the 'static lifetime. Obviously, the function can't accept or return a reference with the 'static lifetime, because it accepts and potentially returns the reference to a local variable, string1.

It's covariance. A reference to a long-living object can be converted to a reference with a shorter lifetime. Again, in the above case, the compiler chooses 'a to be the shorter of the two lifetimes, hence, the lifetime of string1.

2 Likes

how to understand this sentence:“Lifetimes can begin and end at different points than scopes begin and end; and in fact they aren't even required to be contiguous.”

In layman terms: while variable storage is allocated in the beginning of the scope variable value can exist in that storage or not.

Like this:

pub fn main() {
    let mut s;
    println!("Step0");
    s = String::from("Hello");
    println!("Step1");
    consume(s);
    println!("Step2");
    s = String::from("World");
    println!("Step3");
    consume(s);
    println!("Step4");
}

Here s is valid during Step1 or Step3 call, but not when Step0, Step2 or Step4 are happening.

And since many shared references may exist at the same time compiler has some freedom about how it defines lifetimes for various objects.

Lifetimes and scopes are related. For example the lifetime of a refetence to some data is limited by the scope of its owner. A lifetime is generally not limited by the scope of the reference. And also commonly a borrow's lifetime is a lot shorter than the scope of the owner.


Let's illustrate these points:

let r: &str;
{
    let owner = String::from("hi");
    r = &owner;
    // r can be alive here
    println!("{r}");
}
// you may not use `r` here anymore
// because the scope of `owner` has ended
// this would fail:
// println!("{r}");
let r: &str;
let owner = String::from("hi");
{
    let scoped_r = &owner;
    // scoped_r can be used here
    println!("{scoped_r}");
    // let's assign r to scoped_r
    r = scoped_r;
    // a re-borrow would even work as well 
    // same behavior:
    // r = &*scoped_r;
}
// you can use `r` here
// because the scope of `owner` has not ended yet
println!("{r}");
// the scope of scoped_r is irrelevant - it was able
// to have a lifetime bigger than its own scope

I don't think an example of lifetimes being short is required. Short-lived references are super common.


I'm not entirely sure what exact phenomenon @H2CO3 had in mind for non-contiguous lifetimes.

1 Like

The running example in the NLL RFC includes an lifetime that is not contiguous.

4 Likes

The lifetime of the reference returned may not exceed that of 'a. (explicit)

Where does 'a come from might be good question.
longest is a generic function; So the compiler chooses; At the point it is called; variant(*) copies of two argument references are taken.
The second is just a copy of string2: &'static str
The construction of the first reference happens at the . [dot] operator and makes a temporary variable. Call its type &'dot String. Then goes through as_str(), variance comes into play making another temporary; call its type &'elide str. 'elide may not exceed 'dot.
End choice the compiler makes is 'a may not exceed the union of 'elide and 'static.

The construction of the reference is where that magic 'dot happened. It is the start of the compiler tracking how long the borrowing lasts. So ensures the lifetime of result may not exceed the lifetime of string1.

*see @H2CO3 link above.

Thank you, I will read it

thanks a lot :grinning:

thank you very much

thank you very much :smile:

Thank you, I will go to browse