Hi there, great question.
The reason this works at all is because the string slice you’re creating in the function has a static lifetime. "I LOVE Jennifer"
is embedded in the progam binary, so it never “ceases to exists” for the purposes of the program.
But there is more to it than that! Let’s assume a function like this:
fn get_s() -> &str {
let q = "I LOVE Jennifer";
q
}
This is the same function as yours, except I removed the input argument. Now the compiler complains:
missing lifetime specifier
this function’s return type contains a borrowed value, but there is no value for it to be borrowed from
(I don’t know how much you already learned about lifetimes, but I will assume some knowledge so this doesn’t get too long. Just ask if something’s unclear.)
The problem is that just by looking at the function signature, a user of the function or the compiler have no idea how long that string will live:
fn get_s() -> &str {}
The “normal” way to fix this is to tell the compiler the correct lifetime, which is 'static
it is a special lifetime that means “until the end of the program”. So, this one compiles:
fn get_s() -> &'static str {
let q = "I LOVE Jennifer";
q
}
Now the compiler is happy because the lifetime of the string slice is well defined in the function signature.
So why did it work when the argument was present? The key is “lifetime elision”. In some very common scenarios, where it’s pretty clear what the lifetime should be, the compiler infers them for you. In this case, there is one input reference and one output reference. In such a case, the compiler assumes that these two must have the same lifetime. For details on lifetime elision rules, see the book chapter 10.
Since a string slice with lifetime 'static
surely lives at least as long as any input string slice, the lifetime is compatible.
This may be a little abstract, so here’s an attempt to illustrate. Consider this example:
fn main() {
let s;
{
let msg = String::from("I HATE bananas");
s = get_s(&msg);
} // msg goes out of scope
println!("{}", s);
}
fn get_s(m: &str) -> &str {
let q = "I LOVE Jennifer";
println!("{}", m);
q
}
This doesn’t compile. The function get_s
is the same as yours. But I changed the input string msg
to have a limited lifetime. According to lifetime elision rules, the compiler assumes the output of get_s
lives as long as the input argument. Which means, it assumes s
becomes invalid at the same time as msg
does, so it cannot be printed later.
Now, if you change the return type in the example to -> &'static str
, it compiles.