1
votes
fn main() {
    let long; 
    let str1="12345678".to_string();
    {
        let str2 = "123".to_string(); 
        long = longest(&str1, &str2);
    }
    println!("the longest string is: {}", long);
} 

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

gives

error[E0597]: `str2` does not live long enough
 --> src/main.rs:6:31
  |
6 |         long = longest(&str1, &str2);
  |                               ^^^^^ borrowed value does not live long enough
7 |     }
  |     - `str2` dropped here while still borrowed
8 |     println!("the longest string is: {}", long);
  |                                           ---- borrow later used here

My theory is that, since the funtion longest has only one lifetime parameter, the compiler is making both x and y to have the lifetime of str1. So Rust is protecting me from calling longest and possibly receive back str2 which has lifetime less than str1 which is the chosen lifetime for 'a.

Is my theory right?

2
That's exactly that. - Yoric
@Yoric so the rule is to make 'a be the lifetime parameter of the first parameter always? - Guerlando OCs
By using the same 'a for both x and y when you define longest, you specify that 'a is the longest lifetime for which both x and y are guaranteed to live. You're right to do so. If you had simply removed 'a in your declaration, Rust would have basically inferred the same. I'm not sure what you mean by "first parameter", though. - Yoric

2 Answers

2
votes

Let's take a closer look at the signature for longest:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

What this means is that for a given lifetime 'a, both arguments need to last at least for the length of that lifetime (or longer, which doesn't really matter since you can safely shorten any lifetime in this case without any particular difference) and the return value also lives as long as that lifetime, because the return value comes from one of the arguments and therefore "inherits" the lifetime.

The sole reason for that is that at compile time, you can't really be sure whether x or y will be returned when compiling the function, so the compiler has to assume that either can be returned. Since you've bound both of them with the same lifetime (x, y and the return value have to live at least for the duration of 'a), the resulting lifetime of 'a is the smallest one. Now let's examine the usage of the function:

let long; 
let str1 = "12345678".to_string();
{
    let str2 = "123".to_string(); 
    long = longest(&str1, &str2);
}

You have two lifetimes here, the one outside the braces (the main() body lifetime) and the lifetime inside the braces (since everything between the braces is destroyed after the closing brace). Because you're storing the strings as String by using .to_string() (owned strings) rather than &'static str (borrowed string literals stored in the program executable file), the string data gets destroyed as soon as it leaves the scope, which, in the case of str2, is the brace scope. The lifetime of str2 ends before the lifetime of str1, therefore, the lifetime of the return value comes from str2 rather than str1.

You then try to store the return value into long — a variable outside the inner brace scope, i.e. into a variable with a lifetime of the main() body rather than the scope. But since the lifetime of str2 restricts the lifetime of the return value for longest in this situation, the return value of longest doesn't live after the braced scope — the owned string you used to store str2 is dropped at the end of the braced scope, releasing resources required to store it, i.e. from a memory safety standpoint it no longer exists.

If you try this, however, everything works fine:

let long; 
let str1 = "12345678";
{
    let str2 = "123"; 
    long = longest(str1, str2);
}
println!("the longest string is: {}", long);

But why? Remember what I said about how you stored the strings, more specifically, what I said about borrowed string literals which are stored in the executable file. These have a 'static lifetime, which means the entire duration of the program's runtime existence. This means that &'static to anything (not just str) always lives long enough, since now you're referring to memory space inside the executable file (allocated at compile time) rather than a resource on the heap managed by String and dropped when the braced scope ends. You're no longer dealing with a managed resource, you're dealing with a resource managed at compile time, and that pleases the borrow checker by eliminating possible issues with its duration of life, since it's always 'static.

-2
votes

This code for the developer's perspective looks good and it should be because we are printing long in the outer scope so there should be no problem at all.

But Rust's compiler does a strict check on borrowed values and it needs to be sure that every value lives long enough for the variables that depend on that value.

Compiler sees that long depends on the value whose lifetime is shorter that it's own i.e. str, it gives an error. Behind the scenes this is done by a borrow checker.

You can see more details about borrow checker here