Lifetime of a reference returned by the function

Hi.

I can't understand why this code compiles.

fn f<'s, 'r>(s: &'s str, r: &'r str) -> &'r str {
  println!("{s}");
  r
}

fn main( ) {

  let s = "ok!";
  let r;

  {
    let t = "ok too!";
    r = f(s, t);
  } // the end of the `t`'s lifetime

  println!("{r}");
}

As I understand, the t's lifetime ended at the end of the inner block. After this, the data referenced by t — the “ok too!” string slice, should be deleted and by the time the println! macro is expanded, the r reference should be dangled. But this doesn't happen: the code is compiled and works without any problems. Could you explain why?

Thanks.

No, it's a literal, so it's &'static str.

--

Also note that you are not actually taking the address of any local variable. If you replace the string literals with Strings, and actually take addresses, this fails to compile, as expected.

4 Likes

Try

fn f<'s, 'r>(s: &'s str, r: &'r str) -> &'r str {
  println!("{s}");
  r
}

fn main( ) {

  let s = "ok!".to_owned();
  let r;

  {
    let t = "ok too!".to_owned();
    r = f(s, t);
  } // the end of the `t`'s lifetime

  println!("{r}");
}
1 Like

First, Rust lifetimes are generally about the duration of borrows, and not the liveness scope of values. A variable going out of scope is a use of that variable, which does interact with borrow checking. But the lexical scope is not directly tied to a Rust lifetime (those '_ things).

For references in particular, going out of scope almost never matters. It doesn't require the lifetime of the reference type to be alive, and the main thing that is incompatible with a reference going out of scope is having a borrow of the reference itself -- a reference to the reference.

So your inner lexical block doesn't change the borrow checking of your code at all. t goes out of scope at the end of the inner block, but that doesn't effect the lifetime in the type of t. Since you're using literals strs, t's type can be &'static str.

(Clearly, then, the lifetime of a type can be greater than the lexical scope of a value of that type. Indeed, the lifetimes of the types of function arguments are always greater than the function body.)


Your OP is pretty uninteresting to diagram -- all the references can have a 'static lifetime. Here's a variation that compiles with local lifetimes.

fn main() {
    let local = "local".to_string();
    let s = "literal";
    let r;

    {
        // Let's say `t: &'t str` -----+
        let t = &*local; //            |
        r = f(s, t); //                |
    } //                               |
    //                                 |
    println!("{r}"); //                |
    // `'t` can end here --------------+

} // `'t` can't be alive when `local` goes out of scope (it borrows `local`)
  // But that doesn't happen until here, after `'t` needs to be alive

  // The variable `t` wasn't borrowed so it doesn't matter that it
  // went out of scope before the println

In the failing version below t is borrowed when it goes out of scope, hence the error.

fn main() {
    let s = "ok!".to_owned();
    let r;

    {
        let t = "ok too!".to_owned();
        r = f(&*s, &*t);
    }

    println!("{r}");
}
error[E0597]: `t` does not live long enough
  --> src/main.rs:12:22
   |
11 |         let t = "ok too!".to_owned();
   |             - binding `t` declared here
12 |         r = f(&*s, &*t);
   |                      ^ borrowed value does not live long enough
13 |     }
   |     - `t` dropped here while still borrowed
14 |
15 |     println!("{r}");
   |               --- borrow later used here

The primary difference between the two examples is that in the working version, t wasn't borrowed. The value itself contained a borrow (of local), but t itself was not borrowed. In the failing version, t itself was borrowed.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.