Skip to content
Select theme

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.

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:

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.

Delete the braces. Same program, whole file:

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.

The borrow doesn’t blanket the rest of the block either. It stops at the last use, wherever that use happens to sit:

[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.

Ada Lovelace

The 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.

Now move one println!("{first}") from before the push to after it:

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 here
4 | println!("{first}");
5 | scores.push(4);
| ^^^^^^^^^^^^^^ mutable borrow occurs here
6 | 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.

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.