0

The following function fails as I would expect it to. This:

fn return_ref() -> &str {
    let local_ref = &"world"[..];
    local_ref
}

Fails with this:

    Checking rust_learnings v0.1.0 (/home/red/code/rust_learnings)
error[E0106]: missing lifetime specifier
  --> src/main.rs:18:20
   |
18 | fn return_ref() -> &str {
   |                    ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
   |
18 | fn return_ref() -> &'static str {
   |                    ~~~~~~~~

For more information about this error, try `rustc --explain E0106`.
error: could not compile `rust_learnings` due to previous error

However if I add a (specifically) reference to a string in, like so:

fn return_ref(passed_ref: &str) -> &str {
    let local_ref = &"world"[..];
    local_ref
}

My only problem is that the passed in reference warns of not being used, otherwise it runs with no error. How is this possible?

  • 2
    You aren't using the passed in reference, and that argument isn't changing the behavior of the function. It makes it compile, though, because the argument's lifetime is being used to satisfy the "missing lifetime specifier" error via [lifetime elision](https://doc.rust-lang.org/reference/lifetime-elision.html). Because `"world"` is static, you should go ahead and just return a `&'static str`. – Jeremy Meadows Jun 27 '22 at 17:36
  • @JeremyMeadows okay, right, so I would not do this if it wasn't for testing the language. It doesn't only compile, it will run without any fault. Just to be sure of what you're saying: because I pass in this &str, now the returned &str is interpreted as a static variable? – Rasmus Edvardsen Jun 27 '22 at 17:44
  • 1
    I don't know what exactly it is interpreted as, the docs just say that the compiler will "infer a sensible default choice". I can't explain lifetimes as well as somebody else could, but Rust needs *something* to base a reference's lifetime on for the borrow checker, and in your first example, it just had no point of reference. You could also use a lifetime parameter like `fn return_ref<'a>() -> &'a str` to explicitly say "figure it out for me based on the context the function is called in". – Jeremy Meadows Jun 27 '22 at 17:52
  • @JeremyMeadows okay I just tried that and it works as you'd expect - interesting. Can you link me towards some documentation about these default inferences and lifetimes? Thanks – Rasmus Edvardsen Jun 27 '22 at 17:59
  • 1
    this is the only one I know of: https://doc.rust-lang.org/reference/lifetime-elision.html, but here is a SO answer where he breaks down the rules a little differently: https://stackoverflow.com/questions/40325690/what-is-lifetime-elision-in-very-simple-terms – Jeremy Meadows Jun 27 '22 at 18:10

1 Answers1

0

There's no such thing as a reference type without a lifetime attached, but Rust allows us to skip writing out the lifetime in common situations, called lifetime elision.

fn return_ref(passed_ref: &str) -> &str

That code is okay because the compiler sees "one reference in, one reference out" (actually any number of references out) and concludes that this should be treated exactly as if you wrote

fn return_ref<'a>(passed_ref: &'a str) -> &'a str

making the two references' lifetimes the same. But in

fn return_ref() -> &str {

there is no input lifetime to say what the output lifetime should be. Now, given the function's body, we actually do know a lifetime that it can have: 'static, since it's returning a reference to a string literal. But Rust never infers a lifetime (or (almost) any part of a function's signature) based on the function's body (that would make it too easy to change signatures and break callers), so we have to write it explicitly:

fn return_ref() -> &'static str {
    let local_ref = &"world"[..];
    local_ref
}

But what about your last function that did compile with an unused argument? Why is this allowed? Well, the elision works the same,

fn return_ref<'a>(passed_ref: &'a str) -> &'a str {
    let local_ref = &"world"[..];
    local_ref
}

and you are allowed to use a reference as if it had a shorter lifetime. So, the local_ref, when returned, has its 'static lifetime shortened to the 'a lifetime. For an example that makes more sense than the one you came up with, you could write this function:

fn display_value<'a>(value: Option<&'a str>) -> &'a str {
    match value {
        Some(s) => s,
        None => "null",
    }
}

This returns strings from two possible sources depending on the input, and that's fine, since all of the string data lives for at least 'a.

Kevin Reid
  • 37,492
  • 13
  • 80
  • 108