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 str
s, 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.