GoByte Skills Episode 12 of 27, track Rust (3 of 3)

The borrow checker: many readers or one writer

GoByte Skills #12: Rust lets you hold many shared references or one mutable one, never both. That single rule is why push on a borrowed Vec does not compile, and why two threads cannot write the same variable without a lock or an atomic.

Piere is one of GoByte's characters. This post was drafted by AI agents in Piere's voice, then fact checked, run and edited by the GoByte team.

Back to top

At any moment you may have many shared references (&T) or one mutable one (&mut T), never both, which rules out data races at compile time. Call it aliasing XOR mutation. In safe Rust the compiler proves it for every line before your program exists; inside unsafe that proof becomes your job.

Animation for GoByte Skills #12: References are granted and a second writer is refused.
Transcript

Rust code types in while a shared borrow points into a vector, a mutable borrow for push is refused because that reader is still used, and is granted once the lines swap and the reader's last use has passed.

fn main() {
    let mut v = vec![1, 2, 3];
    let first = &v[0]; // shared borrow
    v.push(4); // needs &mut v
    println!("first = {first}");
}

This does not compile. The error is E0502, "cannot borrow v as mutable because it is also borrowed as immutable", and it points at three lines: where the shared borrow starts, where push needs a mutable one, and where first is still used.

Why it is not pedantry#

vec![1, 2, 3] has capacity 3, so push must reallocate: copy into a bigger buffer, free the old one. first would point into freed memory. In C++ the same code compiles, and the bug is called reference (or iterator) invalidation. Here it is a type error.

A borrow lasts until its last use, not the brace#

Since non-lexical lifetimes (the 2018 edition in Rust 1.31, every edition since 1.36), the checker tracks where a reference is last used. Move the println! above the push and it compiles, printing first = 1.

Threads obey the same rule#

Two scoped threads that each run n += 1 both need &mut n, and you get E0499, "cannot borrow n as mutable more than once at a time". No lock, no race, no build. To share mutation you choose a type that allows it through a shared reference and checks at runtime: Mutex, an atomic, or, on one thread, RefCell, which counts borrows and panics on a writer while any other borrow is live.

Limits#

In safe code it rules out data races, not race conditions in your logic, deadlocks or leaks. It is also conservative and rejects some correct code: two &mut self methods returning disjoint fields still conflict, because the signature borrows all of self. Borrow the fields directly, or use helpers like split_at_mut.

Rule of thumb#

Readers share, a writer gets the room alone, and the moment you hold a reference you have promised the data will not move. The borrow checker enforces that promise at 5 pm on a Friday too.

Report a mistake

Your product here? Partner with us

Back to top

Discussion

No comments yet. Signed in GoByte members with a verified e-mail can join. Community guidelines

Reading is open to everyone. Commenting and voting need a GoByte account with a verified e-mail.