← Fundamentals
Guided lesson 8 questions · Rust only

Rust borrowing and references

Lend a value without giving it away, and the rules that keep it safe.

A reference lends a value without moving it. At any moment a value can have many references that only read it, or one reference that can change it, never both. No reference may be used after the value is dropped. The compiler checks these rules before the program runs.

Written for Rust.

Before you begin

Why this matters

Most of the errors new Rust programmers meet are borrow errors, and each one names two places in your code that overlap. Once you can see where a borrow starts and where it ends, you can predict which line the compiler will reject, and choose a fix instead of adding clone until the error goes away.

What this helps you reason about

  • Say where a borrow starts and ends, and predict whether the compiler accepts the next line.
  • Read E0502, E0499, E0505, E0597, E0106, and E0515, and fix each one: reorder, copy, clone, or return an owned value.
  • Write a function that returns one of two references, and say what its signature promises about how long the result may be used.

Rust ownership and moves gave you two ways to hand a String to other code: move it, or clone it. A reference is the third way: it lends the value, and the owner keeps it. In Python, any code holding a list may change it. Rust’s references are checked when the program is compiled: many readers or one writer, and never after the owner is gone. This lesson follows each borrow from where it starts to where it ends, and reads the compiler’s borrow errors one at a time.

Already know this? The short version
  • &x is a shared reference: any number can be in use at once, and none can change x. &mut x is a mutable reference: while it is in use, nothing else may use x, not even its owner.
  • A borrow lasts from where the reference is created to its last use, not to the end of the block. Overlapping uses give E0502 (shared and mutable), E0499 (two mutable), and E0505 (a move while borrowed).
  • A reference may not be used after its value is dropped: E0597 when a value is dropped while still borrowed, E0106 or E0515 when a function returns a reference to its own local. Return the owned value instead.
  • A lifetime annotation such as 'a says which inputs a returned reference may point into. It describes and never extends. With exactly one reference input, or &self, the compiler fills it in for you.
  • The section “What goes wrong with borrowing in real programs?” covers pushing to a Vec inside a loop over it, calling a &mut self method while looping over a field, and the HashMap entry API for get-or-insert.

Go to the final check· Go to the practice tickets

Words in this lesson (8)
reference
A value written &x that lets code use x without owning it. The compiler checks that a reference is never used after x is gone.
borrow
Using a value through a reference, such as &label, without taking ownership. The owner keeps the value.
shared reference
A reference written &x, of type &T. Any number of shared references to a value can be in use at once, and none of them can change it.
mutable reference
A reference written &mut x, of type &mut T, which can change x. While it is in use, x cannot be used in any other way, not even by its owner.
dereference
To reach the value a reference points to, written *r. Rust does it for you when you call a method or read a field through a reference.
lifetime
The part of the program during which a reference may be used. It must end before the value the reference points to is dropped.
dangling reference
A reference to a value that has already been dropped, so the memory it points to may hold something else. Rust rejects every program that could use one.
lifetime annotation
A name such as 'a, written in a function’s signature, that says which inputs a returned reference may point into. It describes lifetimes; it never makes a value live longer.

Examples are focused snippets; imports, enclosing functions, or classes may be omitted. C examples use C11; Haskell examples use Haskell 2010. Expand the notes for language-specific depth.

01 / References

What is a reference?

A reference is a value that lets code use another value without owning it. You create one with &: &word is a reference to word. Using a value through a reference is called borrowing. The owner keeps the value, so nothing moves and nothing is copied.

A function that only needs to look at a String can take &String. The call site shows the borrow too: count_letters(&word). After the call, word is still the owner and still usable. It is dropped at the end of main, as before.

main.rs
fn count_letters(text: &String) -> usize {
    text.len()
}

let word = String::from("borrow");
let size = count_letters(&word); // lend word
println!("{} {}", word, size); // word still owns it

A reference lends a value; the owner keeps it.

Coming from Python: In Python, a function you pass a list to can change that list. In Rust, a function given &word can only read it; to let it change the value, you must write &mut at the call.

Precisely: what a reference holds

At run time a reference to a String is the address of that String. Most functions that read text take &str rather than &String, which the lifetime annotations section introduces; &word turns into a &str automatically when the function asks for one.

Experiment

Lend a String to a function

&word creates a reference that holds the address of word. count_letters reads word through it, and ownership never leaves main.

Why it matters Pass &value when a function only needs to look. The caller keeps the value, and nothing is copied or moved.

Predict what prints, and whether main can still print word after the call. Then remove both & signs, in the parameter type and in the call, and predict again.

fn count_letters(text: &String) -> usize {
    text.len()
}

fn main() {
    let word = String::from("borrow");
    let size = count_letters(&word);
    println!("{} has {} letters", word, size);
}
What to look for

Step into count_letters and look at text. The stepper shows it as an address followed by an arrow and the String it points to: the same "borrow", with the same data= address as word in main’s frame. text holds where word is, not a second String. After the edit, the compiler reports error[E0382]: borrow of moved value: `word`, with “value moved here” under count_letters(word) and “value borrowed here after move” under word in the println! line.

Expected output

borrow has 6 letters

02 / Readers or a writer

Many readers or one writer?

There are two kinds of reference. A shared reference, written &x, lets code read x. Any number of shared references can be in use at the same time. A mutable reference, written &mut x, lets code change x, and only works if x was declared with let mut. While a mutable reference is in use, nothing else may use x: no other reference, and not even the owner.

Together, this is the borrowing rule: many readers or one writer, never both at once. The part of the compiler that checks it is called the borrow checker.

A mutable borrow can come from a method call. scores.push(30) borrows scores mutably, because push changes the Vec. A borrow lasts from where the reference is created to the last line that uses it, not to the end of the block. If a shared reference into scores is still used after the push, the two borrows overlap, and the compiler reports:

error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable

It underlines three places: “immutable borrow occurs here” where the shared reference was made, “mutable borrow occurs here” at push, and “immutable borrow later used here” where the shared reference is used again. The fix is to end the shared borrow before the push: copy the value out, as the experiment does, or move the last use above the push. For a String element, clone it, at the cost of copying the text.

main.rs
let mut scores = vec![10, 20];
// a shared borrow of scores
let first = &scores[0];
// error[E0502] here, because first is used below:
// scores.push(30);
println!("{}", first); // last use of first
// fine: the shared borrow has ended
scores.push(30);
println!("{}", scores.len());

Many shared references, or one mutable reference, never both.

Coming from Python: Python lets you append to a list while a for loop is walking through it; the loop then also visits the new items. A dict is different: adding a key during the loop raises RuntimeError. In Rust, for s in &scores holds a shared borrow for the whole loop, so scores.push inside the loop is rejected with E0502.

Precisely: why push conflicts with a reference

A Vec keeps its elements in one block of heap memory. When push needs more room, it reserves a bigger block, moves the elements there, and frees the old block. A reference to an element would then point to freed memory. The rule forbids this without looking at capacity: any mutable borrow conflicts with any shared borrow in use.

Changing a value through a shared reference is rejected as well. If a function takes text: &String and calls text.push('!'), the compiler reports error[E0596]: cannot borrow `*text` as mutable, as it is behind a `&` reference, and suggests changing the parameter to &mut String.

The error messages in this lesson are quoted as rustc 1.63, the stepper’s compiler, prints them.

Experiment

Read an element, then grow the Vec

An i32 is Copy, so scores[0] copies the number and no borrow remains. &scores[0] is a shared reference into the Vec, and push needs a mutable borrow of the whole Vec while that reference is still used.

Why it matters When you need a small Copy value from a collection you are about to change, copy it out first. For a String, clone it, or finish using the reference before the change.

Predict what prints. Then change let first = scores[0]; to let first = &scores[0]; so first borrows the element instead of copying it, and predict whether the program still compiles.

fn main() {
    let mut scores = vec![10, 20];
    let first = scores[0];
    scores.push(30);
    println!("first {} of {}", first, scores.len());
}
What to look for

Step past each line. first is a plain 10, a copy, and it stays 10 after push. push changes scores from len=2, cap=2 to len=3, cap=4: it had to make room. In this run the data= address stayed the same, but push is allowed to move the elements, and the compiler cannot know which will happen. After the edit, the program does not compile: error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable, with “immutable borrow occurs here” under &scores[0], “mutable borrow occurs here” under scores.push(30), and “immutable borrow later used here” under first in the println! line.

Expected output

first 10 of 3

03 / One writer

Why can’t two parts of my code change it at once?

A mutable reference is the only way to change a value while it is borrowed. To change an i32 through a reference, write *a: the star dereferences a, which means it reaches the value a points to. *a += 5 adds 5 to total. For methods and fields, Rust dereferences for you: if r is a &mut String, r.push('!') needs no star.

The borrowing rule allows one writer at a time. With two, each part of the code could change the value while the other expected it to stay as it was. If a first mutable reference is used again after a second one is created, the compiler reports:

error[E0499]: cannot borrow `total` as mutable more than once at a time

with “first mutable borrow occurs here”, “second mutable borrow occurs here”, and “first borrow later used here”.

Because a borrow ends at the last use of the reference, two mutable references one after the other are fine, as long as the first is not used after the second is created.

main.rs
let mut total = 0;
let a = &mut total;
// last use of a: its borrow ends
*a += 5;
// fine: a is no longer in use
let b = &mut total;
*b += 2;
// error[E0499]: a and b would overlap
// *a += 1;
println!("{}", total); // 7

Two mutable references may follow each other; their uses may not overlap.

Precisely: non-lexical lifetimes

Before the 2018 edition, a borrow lasted until the end of the reference’s block, and the code above was rejected. Today’s rule is called non-lexical lifetimes: the compiler ends a borrow at the reference’s last use. One case lasts longer. If a value holds a reference and its type has cleanup code (it implements Drop), that cleanup runs at the end of the scope and may use the reference, so the borrow lasts until then. The guard that Mutex::lock returns is such a value. Plain references, and structs without cleanup code, follow the last-use rule.

Experiment

Hand over the only writer

a is last used on *a += 5, so its borrow has ended when b is created. The added line uses a again, so the two mutable borrows would overlap.

Why it matters When the compiler reports E0499, look for a use of the first reference after the second was created. Moving that one line is often the whole fix.

Predict the final total. Then add the line *a += 1; after *b += 2; and predict whether it compiles.

fn main() {
    let mut total = 0;
    let a = &mut total;
    *a += 5;
    let b = &mut total;
    *b += 2;
    println!("total {}", total);
}
What to look for

Watch a, b, and total. a and b show the same address, so both point at total. total goes 0, 5, 7. The debugger still shows a after b is created, but the program never uses a again. After the edit, the compiler reports error[E0499]: cannot borrow `total` as mutable more than once at a time, with “first mutable borrow occurs here” under &mut total on the a line, “second mutable borrow occurs here” on the b line, and “first borrow later used here” under *a += 1.

Expected output

total 7

04 / Moving a borrowed value

Why can’t I move it while it is borrowed?

Moving a value while a reference to it is still in use is rejected. A move makes a new owner, and the old place may stop being valid; the reference would be left pointing at it. The compiler reports:

error[E0505]: cannot move out of `name` because it is borrowed

with “borrow of `name` occurs here”, “move out of `name` occurs here”, and “borrow later used here”. The fixes are the ones you know: finish using the reference before the move, or clone the value if you need two.

main.rs
let name = String::from("Ada");
let view = &name;
// error[E0505], because view is used below:
// let moved = name;
println!("{}", view); // last use of view
// fine: nothing borrows name now
let moved = name;
println!("{}", moved);

A value cannot move while a reference to it is still in use.

Precisely: loops and calls

for name in &mut names gives a mutable reference to each element in turn, so each String can be changed in place; this prints ["ada!", "lin!"]. Passing a borrowed value into a function that takes ownership is also a move, and gives E0505 at the call.

main.rs
let mut names = vec![String::from("ada"), String::from("lin")];
// one mutable borrow per element
for name in &mut names {
    name.push('!');
}
// {:?} prints the whole Vec
println!("{:?}", names);

05 / How long

How long can a reference live?

A reference may be used only while the value it points to is alive. The part of the program where a reference may be used is its lifetime, and it must end before the value is dropped. A reference used after its value is dropped is called a dangling reference: it points at memory that may already hold something else. Rust rejects every program that could use one.

If a value is dropped at the end of a block while a reference to it is used later, the compiler reports:

error[E0597]: `word` does not live long enough

with “borrowed value does not live long enough” at the borrow, “`word` dropped here while still borrowed” at the closing brace, and “borrow later used here”.

The same rule stops a function from returning a reference to its own local. The local is dropped when the function returns, so the reference would dangle. fn make_word() -> &String is rejected with error[E0106]: missing lifetime specifier, and the help line “this function's return type contains a borrowed value, but there is no value for it to be borrowed from”. The compiler’s help suggests &'static String. Following it does not help: the compiler then reports error[E0515]: cannot return reference to local variable `word`. Return the owned String instead, which moves it to the caller.

main.rs
let view;
{
    let word = String::from("short");
    view = &word;
} // word is dropped here
// error[E0597]: view would dangle
// println!("{}", view);

// not -> &String
fn make_word() -> String {
    // ownership moves to the caller
    String::from("kept")
}

A reference cannot outlive its value; return the owned value instead.

Coming from C: A C function can return the address of a local variable; compilers often only warn, and the pointer dangles. Rust rejects the program.

Coming from Go: Go keeps a local alive on the heap when you return its address. Rust never does that for you: return the value itself, and ownership moves to the caller.

Precisely: what 'static means

'static is the lifetime of values that live for the whole run of the program, such as a string literal written in the code. A local String is never 'static, so &'static String cannot point to one.

06 / Lifetime annotations

When do I need lifetime annotations?

A string slice, written &str, is a reference to some text: all or part of a String, or a string literal. Functions that read text usually take &str.

When a function returns a reference, the compiler must know which input it points into, so it can check the caller. With one reference input, the answer is obvious. With two, it is not:

error[E0106]: missing lifetime specifier

with the help line “this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`”.

A lifetime annotation answers it. fn longer<'a>(a: &'a str, b: &'a str) -> &'a str names a lifetime 'a and promises that the result points into a or b, so the result may be used only while both are alive. The annotation changes no lifetime. It describes what the function does, and the compiler checks the function and every caller against it.

You rarely write annotations. The compiler fills them in, which is called lifetime elision, when a function has exactly one reference input, or when it is a method taking &self, in which case the result is assumed to point into self. Write 'a when a function returns a reference and has several reference inputs.

main.rs
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if b.len() > a.len() { b } else { a }
}

// one input: no 'a needed
fn tidy(text: &str) -> &str {
    // a &str into text, spaces cut
    text.trim()
}

Annotate a returned reference when several inputs could own it.

Precisely: different lifetimes

When the result can only come from one input, give that input its own lifetime: fn pick_first<'a, 'b>(a: &'a str, b: &'b str) -> &'a str. Then the caller may drop b’s owner while still using the result. In the experiment, longer uses one 'a for both, so the result is limited by the shorter of the two lifetimes, town’s.

Experiment

Return the longer of two place names

longer’s signature says its result may point into a or b, so it is valid only while both are. The compiler checks the signature, not which branch ran. pick could have been town, so pick must not be used after town is dropped.

Why it matters You can predict the compiler’s answer from the signature alone, without knowing which branch will run.

Predict what prints. Then move println!("{}", pick); below the closing brace of the inner block, and predict whether it compiles. pick is "Lisbon", which lives until the end of main.

fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
    if b.len() > a.len() {
        b
    } else {
        a
    }
}

fn main() {
    let city = String::from("Lisbon");
    let pick;
    {
        let town = String::from("Ely");
        pick = longer(&city, &town);
        println!("{}", pick);
    }
}
What to look for

Step into longer and look at a and b. The stepper shows each &str as its text, its length, and a data= address: a is "Lisbon" (len=6) with the same data= address as city, and b is "Ely" (len=3) with town’s data= address. Before the call, pick shows "" (len=0) with an unrelated address: pick has no value yet, and the compiler would reject reading it. After the call, pick is "Lisbon" (len=6) with city’s data= address. After the edit, the compiler reports error[E0597]: `town` does not live long enough, with “borrowed value does not live long enough” under &town, “`town` dropped here while still borrowed” at the closing brace, and “borrow later used here” under pick.

Expected output

Lisbon

07 / In production

What goes wrong with borrowing in real programs?

The rules above explain three errors that Rust programmers meet in larger programs. If the compiler says E0499 or E0502, look here.

Pushing to a Vec inside a loop over it. for job in &jobs holds a shared borrow of jobs for the whole loop, so jobs.push inside the loop gives E0502. Collect the new items in a second Vec and append them after the loop, or loop over positions, as the experiment below does: jobs[i] borrows jobs only for one comparison.

Calling a &mut self method while looping over a field. Inside a method, for item in &self.items { self.note(item); } gives error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable. A &mut self method borrows all of self, including items, because the compiler reads only its signature. Code written directly in the loop body is different: there the compiler tracks each field separately, so self.log.push(item.clone()) compiles, because it borrows only log. Change the other field directly, or pass a helper only that field: note(&mut self.log, item).

Get-or-insert in a HashMap. A function that returns a &'a mut reference into a map, from get_mut or else after an insert, is rejected with error[E0499] and “returning this value requires that `*m` is borrowed for `'a`”. Because one branch returns the reference, the compiler treats the borrow from get_mut as still in use in the branch that inserts, although that branch no longer uses it. The entry API looks up and inserts with one borrow.

main.rs
use std::collections::HashMap;

fn bin<'a>(
    bins: &'a mut HashMap<String, Vec<u32>>,
    key: &str,
) -> &'a mut Vec<u32> {
    // inserts an empty Vec only if key is missing
    bins.entry(key.to_string()).or_default()
}

// counting words: start at 0, then add 1
*counts.entry(word).or_insert(0) += 1;

Loop over positions, change fields directly, and get or insert with entry: never keep a reference into a collection across a change to it.

Precisely: get-or-insert and newer compilers

The get-or-insert function is a known limit of the current borrow checker. rustc 1.63, the stepper’s compiler, rejects it, and so do later stable versions, at least up to rustc 1.90. Polonius, a new borrow checker that the Rust project is developing, is designed to accept it. The entry API compiles with every version.

Experiment

Work through a list that grows

jobs[i] == "wash" borrows jobs only for the comparison, so no borrow is in use when push runs. while i < jobs.len() reads the length before every pass, so the loop reaches the jobs that push added. let job = &jobs[i]; keeps a borrow that the second if still uses after the first push.

Why it matters To add to a Vec while working through it, loop over positions and read jobs[i] where you need it. Do not keep a reference to an element across a push.

Predict what prints. Then add let job = &jobs[i]; as the first line inside the loop, change both jobs[i] in the if lines to job, and predict whether it still compiles.

fn main() {
    let mut jobs = vec![String::from("wash")];
    let mut i = 0;
    while i < jobs.len() {
        if jobs[i] == "wash" {
            jobs.push(String::from("dry"));
        }
        if jobs[i] == "dry" {
            jobs.push(String::from("fold"));
        }
        i += 1;
    }
    println!("{:?}", jobs);
}
What to look for

Watch i and jobs. The first push changes jobs from len=1, cap=1 to len=2, cap=4, and the last data= address, the Vec’s own, changes: the Strings were moved to a new block of memory. A reference to jobs[0] taken before the push would point into the old block. i then reaches 1 and 2, the jobs added during the loop, and the run prints all three. After the edit, the compiler reports error[E0502]: cannot borrow `jobs` as mutable because it is also borrowed as immutable, with “immutable borrow occurs here” under jobs in let job = &jobs[i];, “mutable borrow occurs here” under the first push, and “immutable borrow later used here” under job in the second if.

Expected output

["wash", "dry", "fold"]

08 / Try it

Can you find every borrow?

Find every borrow, say where each one ends, then predict the output.

main.rs
fn add_bonus(score: &mut i32) {
    *score += 10;
}

fn bigger<'a>(a: &'a i32, b: &'a i32) -> &'a i32 {
    if b > a {
        b
    } else {
        a
    }
}

fn main() {
    let mut red = 40;
    let blue = 45;
    add_bonus(&mut red);
    let best = bigger(&red, &blue);
    println!("{} {} {}", red, blue, best);
}
Reference: the borrow errors in this lesson

E0382, borrow of moved value: a value is used after it moved. Lend it with & instead, clone it, or make the move its last use.

E0502, borrowed as mutable and immutable: a change happens while a shared reference is still used. Use the reference before the change, or copy or clone the value out.

E0499, borrowed as mutable more than once: two mutable references are in use at once. Finish with the first before creating the second.

E0505, cannot move out because it is borrowed: a move happens while a reference is still used. Use the reference first, then move, or clone.

E0597, does not live long enough: a value is dropped while a reference to it is still used. Use the reference inside the value’s block, or declare the value in an outer block.

E0106 and E0515, a returned reference with nothing to point into: the function returns a reference to its own local. Return the owned value; if the result points into one of several inputs, add a lifetime annotation.

What prints? Would the program still compile if add_bonus(&mut red); were moved down to just above the println! line?

Reveal the reasoning

50 45 50. add_bonus borrows red mutably for the call and adds 10 through *score, so red is 50 before bigger runs. bigger borrows red and blue as shared references and returns the one pointing at red, so best shows 50. If add_bonus moved below let best, it would need a mutable borrow of red while best, a shared borrow that might point at red, is still used by println!. The compiler rejects it with error[E0502]: cannot borrow `red` as mutable because it is also borrowed as immutable.

Practice tickets

  1. RUST-201 · Make lane scores stick → Free
  2. RUST-202 · Keep the order total up to date → Sign in · Pro
  3. RUST-203 · Some follow-up jobs never reach the crew’s list → Sign in · Pro

Keep exploring

Rust lifetimes and relationships →

Primary references