Aren't these error messages appearing inconsistently?

Hello, I'm a beginner still learning the language.

I have two similar programs, which don't both compile even though they're very similar, and don't understand why.

Program #1 compiles and runs without error:

fn main() {
    let s = get_it("hello");
    println!("s = {}", s);
}

fn get_it(_w: &str) -> &str {
    let w = "Hi there";
    &w[0..2]
}

(outputs: s = Hi)

Program #2 doesn't compile however, even though not much has changed (only removed the parameter from the second function, which was unused anyway):

fn main() {
    let s = get_it();
    println!("s = {}", s);
}

fn get_it() -> &str {
    let w = "Hi there";
    &w[0..2]
}

Throws this error message:

error[E0106]: missing lifetime specifier
 --> src/main.rs:6:16
  |
6 | fn get_it() -> &str {
  |                ^ help: consider giving it a 'static lifetime: `&'static`
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Isn't this an inconsistency in the compiler? Shouldn't the two programs either both compile or both "not compile"?

Thanks.

What you're seeing is lifetime elision. All references in all functions have a lifetime associated with them, but in some cases the compiler will guess them for you. The main rule of lifetime elision is that when you have one argument with an unspecified lifetime, then that is linked to the lifetime in the return argument like this:

fn get_it<'a>(_w: &'a str) -> &'a str {
    let w = "Hi there";
    &w[0..2]
}

When there is no argument and only a return, no lifetime elision takes place, and you have to explicitly specify the lifetime. Note that linking the two lifetimes together mean that the returned string cannot outlive the argument, even though you are returning a slice into a string literal that lives forever. This has the consequence that this does not compile:

fn get_it(_w: &str) -> &str {
    let w = "Hi there";
    &w[0..2]
}

fn main() {
    let s;
    {
        let my_string = String::from("Hello world");
        s = get_it(&my_string);
    } // oops, my_string went out of scope
    println!("{}", s);
}

playground

However, since the returned string actually is a reference into a string literal, you can unlink the two lifetimes like this, which means that this will compile:

fn get_it(_w: &str) -> &'static str {
    let w = "Hi there";
    &w[0..2]
}

fn main() {
    // This is ok!
    let s;
    {
        let my_string = String::from("Hello world");
        s = get_it(&my_string);
    }
    println!("{}", s);
}

playground

In short, you have to specify the lifetime because there is nothing for it to be automatically linked to.

2 Likes