Borrow checking is done at compile time, but whether there are any actual references around is a runtime property ... if the compiler even understood what Vec
does on that level, which it does not.
But let me try to explain why the String
must still be borrowed from another perspective.
let mut v: Vec<& /* 's */ str> = Vec::new();
The type of v
is Vec<&'s str>
for one specific lifetime, 's
, that the compiler is going to try to infer. Every &str
we push into the Vec
has to have that same lifetime so that the types match.
That means every borrow we push into v
has to be valid for at least 's
.
If it helps, think of it as a Vec<T>
where T = &'s str
. Naturally, you can only push T
s into a Vec<T>
.
With that in mind...
// 's has to be alive wherever we use it
v.push(&s); // ------------------------------+ we start borrowing
// 's has to be alive | `s` up here and it
v.drain(..).for_each(|s| println!("{s}")); // | has to be borrowed
// | for `'s`, which
s = String::from("Bar"); // X | has to live until
// 's has to be alive | at least here --+
v.push(&s); // -------------------------------+ <---------------'
The borrow of s
on the first push
has to still be active at the second push
, because otherwise we would be pushing references with two different lifetimes into the Vec
-- that is, we would be trying to push values with two different types into v
. We'd be trying to push a U
into our Vec<T>
.
So even if the compiler could understand that the Vec
is empty, the types involved require that s
is borrowed from the first push
all the way to the second push
, so that we are pushing T
s into our Vec<T>
. That means it's borrowed on the line I've marked X
-- where you overwrite s
. But overwriting something while it is borrowed is an error.
If you want to store borrows of different durations, you need a new Vec
, because a Vec<&'_ str>
can only store borrows of a single duration.
Here's a good YouTube video that looks at a related lifetime error from the perspective of types.