How long does a borrow last?
Short answer: A borrow lasts from the & to the last use of the reference — the last line that
reads it — not to the end of the block the reference was declared in. The compiler calls this
non-lexical lifetimes: a write after the final read is fine, even though the reference’s binding is
still in scope.
The scope you thought it had
Section titled “The scope you thought it had”Start from a fresh project — cargo new borrow-length, then replace src/main.rs with the whole of
each file below. The project name doesn’t matter, because nothing in the output depends on it.
The shape older material teaches wraps the read in a block of its own:
fn main() { let mut scores = vec![1, 2, 3];
{ let first = &scores[0]; println!("{first}"); }
scores.push(4);
println!("{scores:?}");}1[1, 2, 3, 4]The inner block ends the borrow at its closing brace, so the push that follows can’t overlap it.
Until non-lexical lifetimes arrived — Rust 1.31, with the 2018 edition — that block wasn’t
decoration: borrows followed lexical scope, meaning a reference was live from its & to the end of
the enclosing block whether or not anything read it again. NLL replaced that with the rule in the
short answer, and every compiler since has worked this way.
Where the borrow actually ends
Section titled “Where the borrow actually ends”Delete the braces. Same program, whole file:
fn main() { let mut scores = vec![1, 2, 3]; let first = &scores[0]; println!("{first}");
scores.push(4);
println!("{scores:?}");}1[1, 2, 3, 4]Same output, same compile. The shared borrow of scores ends at the println!("{first}") — its
last use — so the push on the next line never overlaps a live borrow. The binding first is still
there afterwards; a reference outliving its own borrow is normal. Reading first again is what you
can’t do, because the borrow stretches to meet the read.
Last use, not last line
Section titled “Last use, not last line”The borrow doesn’t blanket the rest of the block either. It stops at the last use, wherever that use happens to sit:
fn main() { let mut scores = vec![1, 2, 3]; let first = &scores[0];
if *first > 10 { scores.push(4); }
println!("{scores:?}");}[1, 2, 3]The borrow covers the condition *first > 10 and stops there. The push inside the body runs after
the last use of first, so it’s legal — even though first was declared before the if and the
body sits in the same block. Under the lexical model this exact program fails: the borrow would run
to the closing brace of main, inner braces or not.
A &mut borrow ends the same way
Section titled “A &mut borrow ends the same way”fn main() { let mut name = String::from("Ada"); let writer = &mut name; writer.push_str(" Lovelace");
let reader = &name; println!("{reader}");}Ada LovelaceThe exclusive borrow ends at writer.push_str(" Lovelace"), its last use, so taking &name
afterwards is fine. NLL isn’t a concession to readers: a &mut lasts exactly as long as it’s used,
and no longer.
It stretches to the furthest use
Section titled “It stretches to the furthest use”Now move one println!("{first}") from before the push to after it:
fn main() { let mut scores = vec![1, 2, 3]; let first = &scores[0]; println!("{first}"); scores.push(4); println!("{first}");}error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable --> src/main.rs:5:5 |3 | let first = &scores[0]; | ------ immutable borrow occurs here4 | println!("{first}");5 | scores.push(4); | ^^^^^^^^^^^^^^ mutable borrow occurs here6 | println!("{first}"); | ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.One line moved, and the program from earlier on this page is an error. The diagnostic is the rule in the compiler’s own words: the borrow is “later used” on the last line, so it’s still alive at the push. Move the read back above the push and it compiles again — the borrow is exactly as long as the uses make it, and not one line more.
What this means for signatures
Section titled “What this means for signatures”A reference type still carries a lifetime — &'a T — and a function that returns a reference still
has to spell it out. What NLL changed is where the compiler works out 'a inside a function body:
from the uses, not from the braces. Writing those annotations by hand is the subject of the
lifetimes chapter.
Related
Section titled “Related”- What are the rules of borrowing? — the two rules the duration has to fit inside
- Why does Rust allow many immutable borrows but only one mutable? — the E0502 above, and the aliasing behind it
- What is the borrow checker? — the pass that works out where each borrow ends
- What is reborrowing? — a borrow that lasts for the length of one call
- Borrowing — the rest of this chapter
- Next in this chapter: how to borrow one field of a struct