How to end a borrow early
Short answer: A borrow ends at the last use of the reference, not at the end of the block. So a
borrow that “won’t die” is really a reference — or a value holding one, like the &i32 inside an
Option<&i32> — that is still used further down. End it early by closing an inner scope around the
borrow, by dropping the reference once the work is done, or by taking the value instead of the
reference.
The borrow that outlives its line
Section titled “The borrow that outlives its line”Start from a fresh project — cargo new end-borrow-early, then replace src/main.rs with the whole
of each file below.
fn main() { let mut scores = vec![10, 40, 30]; let first = scores.first(); scores.push(0); println!("{first:?}");}error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable --> src/main.rs:4:5 |3 | let first = scores.first(); | ------ immutable borrow occurs here4 | scores.push(0); | ^^^^^^^^^^^^^^ mutable borrow occurs here5 | println!("{first:?}"); | ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.Read the spans in the error. The borrow of scores did not end on line 3, where it was created, and
it did not end when line 3 finished — it reaches all the way to line 5, because first is used
there. first is an Option<&i32>: it holds a reference to inside the vector, and while that
reference is still alive, so is the borrow. The push on line 4 needs &mut scores, and it lands in
the middle of a live borrow. The reference outlived the line that made it, and it dragged the borrow
along.
(How long does a borrow last? covers the last-use rule in general. This page is about what to do when the rule works against you.)
End it with a scope
Section titled “End it with a scope”Braces end a borrow at a point you choose. Everything that needs the reference goes inside a block;
at the closing brace the reference dies, the borrow ends, and scores is free again:
fn main() { let mut scores = vec![10, 40, 30]; { let first = scores.first(); println!("first was {first:?}"); } scores.push(0); println!("{scores:?}");}first was Some(10)[10, 40, 30, 0]The block costs nothing at run time. It only marks the region where the borrow should live, the same
way the borrow of a let ends when the variable’s own block ends. This is the pattern to reach for
when the borrow genuinely covers several statements and they belong together — read through the
collection inside the block, mutate it after.
End it with drop
Section titled “End it with drop”The other tool is drop, applied to the reference itself:
fn main() { let mut scores = vec![10, 40, 30]; let top = scores.last_mut().expect("not empty"); *top += 5; drop(top); scores.push(0); println!("{scores:?}");}[10, 40, 35, 0]It looks odd — dropping a reference? — but it compiles and it works. drop(top) moves the reference
into drop, which makes that call the reference’s last use, so the mutable borrow of scores ends
on line 5 and the push on line 6 is legal.
rustc does have something to say about it:
warning: calls to `std::mem::drop` with a reference instead of an owned value does nothing --> src/main.rs:5:5 |5 | drop(top); | ^^^^^---^ | | | argument has type `&mut i32` | = note: `#[warn(dropping_references)]` on by defaulthelp: use `let _ = ...` to ignore the expression or result |5 - drop(top);5 + let _ = top; |
warning: 1 warning emittedThe warning is right about run time and silent about the borrow. At run time, dropping a reference does nothing: a reference is a pointer, dropping it frees nothing, and the vector is untouched. The borrow still ends, because for the borrow checker the drop counts as the reference’s final use.
That also states the limit. drop cannot rescue a reference the code still needs: if top were
used after the push, the borrow would reach past the drop, because a later use is a longer borrow.
And since the borrow would have ended at top’s last use anyway, drop on a plain reference mostly
makes the ending explicit instead of implicit. Where it does real work is on values that hold a
borrow with a run-time side to it — the guard returned by RefCell::borrow_mut, for instance, keeps
the cell borrowed until the guard is dropped, and drop(guard) releases it in the middle of a
block.
Take the value instead
Section titled “Take the value instead”Often the cleanest fix is to stop holding the reference. If all you need from first is the number,
take the number:
fn main() { let mut scores = vec![10, 40, 30]; let first = scores.first().copied(); scores.push(0); println!("{first:?}"); println!("{scores:?}");}Some(10)[10, 40, 30, 0].copied() turns Option<&i32> into Option<i32>: the value comes out of the borrow, the
reference dies on the spot, and the borrow of scores ends on the line it was made. The same trick
works for a mutable borrow — let bumped = *top; copies the i32 out, and that copy is top’s
last use.
This is the shape to look for when a borrow “won’t die”: a reference being kept around for later
when the later code only wants the value. For Copy types like i32, copying the value out is free
and ends the borrow immediately. For types that are not Copy, the scope or the reordering is the
fix — and if you find yourself wanting to clone a String out of a borrow, that is the signature
question from the previous article, not a borrow-lifetime one.
Related
Section titled “Related”- How long does a borrow last? — the last-use rule this page leans on
- What is the borrow checker? — the pass that produces E0502
- When should you take a reference instead of cloning? — holding a reference vs owning a copy
- Borrowing — the rest of this chapter