First an aside: due to deref coercion, this does the same thing as your OP:
{
let b = "Doe";
user.name = b; // No &b
// or if you prefer, `user.name = &**&b`.
}
The &b
in your OP -- a &&str
-- is a temporary that exists on that one line only. You're assigning a &str
to user.name
.
Though the OP can be explained due to how &'static str
literals work, I believe there may be a deeper misunderstanding here, which I will attempt to address. It ended up being sort of long, so if you're satisfied, perhaps skip it for now.
References going out of scope is almost entirely a no-op. It doesn't cause their borrow to be active at that point, and the only thing it conflicts with is the reference itself being actively borrowed -- as in you have a nested reference. That's not the case in the OP -- you don't have a &&str
in user.name
or anywhere else outside of the inner block. (And you didn't need the &&str
to begin with.)
Effectively you overwrote a &'static str
. As the others have explained, the actual str
data of the literals are static -- baked into the executable -- for this particular case.
But let me point out that you don't need &'static
for similar things to work. This time, the owned data is not static, but it still lasts longer than the reference in the inner block. In the assignment to user.name
, I'm not borrowing the reference itself. I'm reborrowing its referent (doe
, and ultimately the str
data that doe
owns).
That reborrow can outlast the scope of the original reference (temp
).
Rust lifetimes (those '_
things) are a type-level property about the duration of borrows. Despite the unfortunate overlap in terminology, they are not directly about the liveness scope of variables or other values -- and especially not about the liveness/lexical scope of references.
My playground demonstrates that the lifetime (borrow duration) in the type of a reference is not limited by the scope of the reference variable. Otherwise you couldn't put a &'static str
into a local variable, say. Moreover, this function would not compile if that was the case:
fn example<'m>(s: &'m mut User<'_>) -> &'m i8 {
s.age += 1;
&s.age
}
Because s
goes out of scope at the end of example
, but a reborrow with the same lifetime as s
is returned. The referent -- the User<'_>
-- remains borrowed so long as the return value is in use.
Similarly, temp
going out of scope didn't limit how long the lifetime in its type -- the borrow of doe
-- could be. And temp
itself was not borrowed in the assignment; *temp
was reborrowed.
Most learning material, including The Book, try to give the gist of borrow checking by heavy analogy to scopes. But -- with the exception of nested borrows (&&_
) -- references going out of scope doesn't really matter to borrow checking. The borrow checker looks at where values are used to figure out where borrows are active, and then looks again at every use to see if there are any actions that conflict with active borrows.
Going out of scope (and potentially running a destructor when going out of scope) is a type of use which can conflict with being borrowed. That is the interaction between scopes and the borrow checker. But a reference going out of scope is practically a no-op; they have no destructor. The variable holding the reference itself becomes uninitialized, which is why you can't have an active reference-to-a-reference. But that's the only effect.
The borrow in the type of temp
-- the borrow of doe
-- is kept active through the last use of user
, due to the assignment to user.name
. That is, the borrow is active through the println!
.
The only use that happens at the end of the inner block is that temp
goes out of scope. I'm not trying to keep a borrow of temp
itself alive -- I have no &temp: &&str
or &temp: &&String
I'm using anywhere -- so there is no conflict at the end of the inner block.
Being destructed is a use which is incompatible with being borrowed (as are other things, such as being moved or having a &mut _
taken). So doe
going out of scope and destructed at the end of main
is a use which is incompatible with being borrowed. But since the borrow can end after the println!
and before the end of main
, there is no borrow check error there either.
If you put doe
inside the inner block, it gets destructed at the end of the inner block instead, which results in a borrow check error.
It's the scope -- and other uses -- of the borrowed doe
which matter in the example, not the scope of temp
.