Search code examples
rustparametersreferencereturn-value

Returning reference only allowed when passing in a reference


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?


Solution

  • 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.