Why doesn't longest function work?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7805cb32b9bfddc747cc9e1005914f69

I am not sure why compiler can't figure out string1 and string2 live long enough.. They are in the same scope, they won't get dropped.

The compiler can't figure out which &str you are returning. In this usage they're in the same scope and have roughly the same lifetime, but that might not always be the case.

The error message helpfully comes with a hint which describes how to fix it:

  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
1 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ^^^^    ^^^^^^^     ^^^^^^^     ^^^

By giving both stings the same lifetime parameter, you're saying that you're returning a string that lives as long as the shortest lifetime of the two input strings.

1 Like

I don't think that is true.

Neither of those strings are in scope of the longest() function. They are created, and destroyed, in the scope of the caller. Or perhaps the callers caller, or the callers callers caller....longest() only has references to them.

As far as I understand the compiler/borrow checker only considers the function it is compiling when it is checking the rules. It does not do whole program analysis to figure out the actual lifetimes of everything passed into the function.

As such, whilst it is busy checking your longest() function, you are returning either one of two strings, so they had better have the same life times.

Consider the following:

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

fn some_thing(string1: &str) -> &str {
    let string2 = "xyz";

    longest(string1, string2)
}

fn main () {
    let string1 = "xyz";
    some_thing(string1);
}

Now string1 and string2 do not actually have the same lifetimes. They are in different scopes. string2 is dead when some_thing() returns. But string1 lives as long as main().

The borrow checker does not know that when it is looking at longest().

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d28ae9c5bc373ffc00ac7da037fa40eb

Somebody shout if I am wrong about this. I'm just getting the hang of those tick marks myself.

5 Likes

Your intention is right but the example is a little bit wrong. Because string literals in Rust (like "xyz") are of type &'static str. So actually string1 and string2 have the same lifetime, namely 'static, in your example.

To show the benefit of lifetimes I suppose to change fn some_thing():

// 'i is not needed but clarifies things
fn some_thing<'i>(string1: &'i str) -> &'i str { 
    let string2 = String::from("xyz");

    longest(string1, &string2)
}

Now the example fails to compile :+1:. This is because some_thing() guarantees the the Rust compiler to return something that lives at least as long as the input string1 (note that the returned value does not have to depend on the input!). However, longest() may return string2 which lifetime is only the scope of some_thing() therefore we get an error:

error[E0515]: cannot return value referencing local variable `string2`
3 Likes

HAHAH I understand, but how would you fix that issue? You declared lifetimes, but compiler doesn't like it because function returns reference of a something (string2).

Hmm...Perhaps what I was getting at with my example is better expressed as:

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

fn some_thing(string1: & str) -> usize {
    let string2 = "xyz".to_string();

    longest(&string1, &string2).len()
}

fn main () {
    let string1 = "abcd".to_string();
    let length = some_thing(&string1);
    println!("some thing = {}", length);
}

Where string1 and string2 really are strings with a lifetimes only of the scope (functions) they are created in.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2386f8c9e4861a32d25d0e75f0595fcf

I don't understand the question. I fixed the problem by putting lifetime ticks on what you started with. Both my examples compile, although I think the second one makes my point better.

I don't know what else I could do.

Of course one cannot return a reference to some local String from a function, because it is dead when the function returns and the reference would be bad.

If you want such a locally created string to outlive the function it is created in you have to wrap it in a smart pointer and return that smart pointer.