Beginner issue about &str borrow and lifetime

#1

fn main() {
	let mut s = String::from("hello world");
	let word = first_word(&s);
	s.clear();
}

fn first_word(s: &str) -> &str  {
	return "ddd";
}

#2

fn main() {
	let mut s = String::from("hello world");
	let word = first_word(&s);
	s.clear(); 
}

fn first_word<'a>(s: &str) -> &'a str  {
	return "ddd";
}

Why #1 can not work, and #2 work correctly, since I think param s is always a reference and will end its borrow at the end of the function first_word.

The first_word signature in the first version desugared is as follows:

fn first_word<'a>(s: &'a str) -> &'a str;

That signature means that the returned string potentially borrows from the argument, so as long as the returned reference is alive, the argument would be considered borrowed. Now Rust checks two things:

  1. If the function first_word obeys the signature: Yes, it obeys (since static strings are valid for any lifetime).
  2. Based on the first_word signature, is the main function correct? No, because word, which may borrow from s is alive while you're calling clear. Note that this rule may be changed slightly in the future – the word may be considered dead on the call to clear (because it's not used later) and you'd get an error only in case you'd try to use word after clear.

In #1, both lifetimes in the signature of first_word are omitted. The compiler follows the lifetime elision rules to insert lifetimes, making the code equivalent to

fn first_word<'a>(s: &'a str) -> &'a str;

Since the argument type and the return type have the same lifetime, the argument must remain borrowed as long as the returned reference is live.

In #2, only one lifetime is elided. The elisions rules say that this signature is equivalent to

fn first_word<'a, 'b>(s: &'b str) -> &'a str;

In this case, the two lifetimes are unrelated, so each borrow can be as short or as long as necessary, regardless of how long the other lasts.

I think #2 pretty much implies that 'a = 'static. Since the returned lifetime is not related to the input arg, it needs to manufacture a reference "out of thin air". Maybe a clearer example is:
fn foo<'a>() -> &'a str

Where could the &str come from in this case? Just 'static, AFAIK.

1 Like