Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Rust (2024 edition)  Module 3 – Ownership, borrowing and slices

References and the borrowing rules

Borrow Rust values with shared and mutable references, follow the rule of many readers or one writer, and see why a borrow ends at its last use.

Free core, Pro depth: 8 of 20 minutes free
Free: A reference lets code use a value without owning it · The borrowing rules: any number of readers, or one writer · A borrow lasts until its last use, not until the end of the block. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Use shared and mutable references to lend a value, and say what each one allows
  • Apply the rule of many shared references or one mutable reference to predict E0502 and E0499
  • Fix a borrow error by ending the borrow, at its last use, before the value changes
  • Explain what a two-phase borrow is, and predict which method calls it allows

Before you start

What you’ll learn · Before you start

What you will learn

  • Use shared and mutable references to lend a value, and say what each one allows
  • Apply the rule of many shared references or one mutable reference to predict E0502 and E0499
  • Fix a borrow error by ending the borrow, at its last use, before the value changes
  • Explain what a two-phase borrow is, and predict which method calls it allows

Before you start

On this page

let first = &queue[0]; followed by queue.push(50); is two lines that Rust refuses with error E0502. The push needs a fifth place in a Vec that has room for four, so it may move all four items to a new block of memory, as a run below shows, and first would then point into the old block, which has just been freed; in C++, which accepts the same lines, the standard calls such a reference invalidated.

This lesson shows how references let code use a value without owning it, the two borrowing rules that every reference follows (any number of readers or one writer, and no reference to a value that is gone), and why the order of your lines decides what compiles. The Rust programs here ran on Rust 1.99.0.

A reference lets code use a value without owning it

A reference: A value that refers to another value without owning it: &T lets code read the value and &mut T lets it change it. The compiler checks that no reference outlives the value it refers to. is an address that comes with a promise: &marks refers to marks without taking it, and the compiler guarantees that it refers to a valid value for as long as it is used. Passing &marks to a function lends the value, and when the function returns the loan is over. A shared reference: A reference written &T. Any number of them may refer to a value at the same time, and none of them can change it., &T, lets the borrower read; a mutable reference: A reference written &mut T, through which code can change the value it refers to. While it is in use, no other reference to that value may be used., &mut T, lets it change the value in place, and only an owner declared with mut can lend one:

borrow.rs: lending the marks to read and to change Rust · borrow.rs
// A shared borrow to read, a mutable borrow to change: main owns the marks throughout.
fn total(marks: &[u32]) -> u32 {
    marks.iter().sum()
}

fn add_bonus(marks: &mut Vec<u32>, bonus: u32) {
    for mark in marks.iter_mut() {
        *mark += bonus;
    }
    marks.push(bonus);
}

fn main() {
    let mut marks = vec![72, 45, 88];
    println!("total {}", total(&marks));
    add_bonus(&mut marks, 5);
    println!("total {}", total(&marks));
    let first = &marks[0];
    let second = &marks[1];
    println!("first {first}, second {second}, both read at once");
    let last = marks.last_mut().unwrap();
    *last = 10;
    println!("marks {marks:?}, total {}", total(&marks));
}

Output

total 205
total 225
first 77, second 50, both read at once
marks [77, 50, 93, 10], total 230

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O borrow.rs && ./borrow

Step through the loans in order:

Interactive · message by message Who lends what to whom in borrow.rs Step through main. Each message is a loan of the marks, or the answer that comes back when the loan ends.
Who lends what to whom in borrow.rs, step 7 of 7. Messages so far: main to total(): &marks, to read; total() to main: 205; main to add_bonus(): &mut marks, to change; add_bonus() to main: loan over.mainowns 77, 50, 93, 10total()takes &[u32]add_bonus()takes &mut Vec<u32>&marks, to read205&mut marks, to changeloan over

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. main owns the marks. let mut marks = vec![72, 45, 88] makes main the owner of the marks, as in the lessons before.
  2. A loan to read. total(&marks) lends the marks through a shared reference. total can read them but not change them, and it adds them up: 205.
  3. The loan ends. When total returns, its reference is gone, and main has its marks back to itself. Nothing was moved or copied but an address.
  4. A loan to change. add_bonus(&mut marks, 5) lends them through a mutable reference: the function adds 5 to each mark and pushes a fifth value, 5, onto the Vec.
  5. Changed in place. The change happened to the Vec that main owns, so when add_bonus returns, the next total is 225.
  6. Two readers at once. first and second are two shared references into the marks, both in use in one println!. Any number of readers may share a value.
  7. One writer. marks.last_mut() gives one mutable reference to the last mark, and *last = 10 changes it through that reference. The marks are 77, 50, 93 and 10, total 230.

main owned the marks the whole time. total read them through &[u32], a borrowed view of the items, and add_bonus changed them through &mut Vec<u32>, which can also push. Method calls see through a reference, so marks.iter() reads a Vec and a reference to one alike, while *mark += bonus writes through a reference with an explicit *.

Checkpoint

Check: Lending a value with & and &mut

  1. Question 1 of 2 What does the call total(&marks) do to marks?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: It lends marks to total, which reads it through a reference, and main still owns it

    &marks is a borrow: total can use the marks for the length of the call, and main never loses them.

  2. Question 2 of 2 Which two things does the call add_bonus(&mut marks, 5) need in order to compile?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • marks declared with let mut
    • The argument written as &mut marks

    A change goes through two permissions: the binding declared mut, and the borrow written &mut.

The borrowing rules: any number of readers, or one writer

Every borrow: To take a reference to a value, so that other code can use the value while its owner keeps it. A borrow lasts from where the reference is made to the last place it is used. follows two borrowing rules. The Rust book states the first one this way:

At any given time, you can have either one mutable reference or any number of immutable references.

The second is that references must always be valid. The borrow checker: The part of the Rust compiler that checks every reference against the ownership and borrowing rules, and refuses to build a program in which a reference could outlive its value or break those rules. applies both borrowing rules before the program is built, so the program from the opening never runs:

push_conflict.rs: a reference held across a push Rust · push_conflict.rs
// A reference into a Vec, held across a push that needs more room than the Vec has.
fn main() {
    let mut queue: Vec<u64> = Vec::with_capacity(4);
    queue.extend([10, 20, 30, 40]);
    let first = &queue[0];
    queue.push(50);
    println!("first is {first}");
}

Output (exit status 1)

Printed as an error (standard error)

error[E0502]: cannot borrow `queue` as mutable because it is also borrowed as immutable
 --> push_conflict.rs:6:5
  |
5 |     let first = &queue[0];
  |                  ----- immutable borrow occurs here
6 |     queue.push(50);
  |     ^^^^^^^^^^^^^^ mutable borrow occurs here
7 |     println!("first is {first}");
  |                         ----- immutable borrow later used here

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0502`.

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O push_conflict.rs && ./push_conflict

E0502 names the two borrows that overlap: first, made on line 5 and used on line 7, and the mutable borrow that push needs on line 6. The reason for the rule shows when the same push runs with no reference held:

buffer_moves.rs: the items before and after the push Rust · buffer_moves.rs
// Where the Vec keeps its items before and after a push that needs more room.
fn main() {
    let mut queue: Vec<u64> = Vec::with_capacity(4);
    queue.extend([10, 20, 30, 40]);
    let neighbour: Vec<u64> = vec![0; 4];
    let before = queue.as_ptr();
    println!("length {}, capacity {}", queue.len(), queue.capacity());
    queue.push(50);
    let after = queue.as_ptr();
    println!("length {}, capacity {}", queue.len(), queue.capacity());
    println!("items moved to a new block: {}", before != after);
    println!("{} items beside it", neighbour.len());
}

Output

length 4, capacity 4
length 5, capacity 8
items moved to a new block: true
4 items beside it

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O buffer_moves.rs && ./buffer_moves

The capacity went from 4 to 8, and the items moved to a new block; the standard library documents that when a vector’s length exceeds its capacity, its elements have to be reallocated. Step through what first would have pointed at:

Interactive · step by step Why push_conflict.rs is refused Step through the three lines. The left column is the stack, the right column the heap blocks of the Vec.
Why push_conflict.rs is refused, step 6 of 6. Showing queue: a Vec (capacity 4, length 4), first: &u64 (used before the push), Block A (10, 20, 30, 40). In focus: queue: a Vec, first: &u64.ownsqueue: a Veccapacity 4, length 4first: &u64used before the pushBlock A10, 20, 30, 40

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A full Vec. queue holds 10, 20, 30 and 40 in heap block A, which has room for exactly 4 items: capacity 4, length 4.
  2. A reference to item 0. let first = &queue[0]; is a shared borrow: first holds the address of the first item, inside block A.
  3. A push needs room. queue.push(50) needs a fifth place. The Vec gets block B, with room for 8 items, copies the four items over, adds 50 and frees block A. buffer_moves.rs ran this push: capacity 8, and the items in a new block.
  4. first would dangle. first still holds the old address, inside block A, which has been freed. Reading it on line 7 would read memory the program no longer owns.
  5. rustc refuses. None of that ever runs. push needs a mutable borrow of queue while the shared borrow in first is still to be used, which the rules forbid, so rustc reports E0502 and builds nothing.
  6. The fix. In last_use.rs, first is printed before the push. Its borrow ends at that last use, so the push may move the items: no reference is left to dangle.

Two mutable references to one value, both in use at once, break the same rule from the other side, and rustc reports them as error E0499. A narrow exception, two-phase borrows, lets a method call such as v.push(v.len()) read the vector in its own argument.

Key idea

While a shared reference into a value is still to be used, nothing may change the value, and while a mutable reference is in use, nothing else may touch it: many readers or one writer, checked before the program runs.

Checkpoint

Check: Many readers or one writer

  1. Question 1 of 2 Which of these may be in use at the same time, for one Vec?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Three shared references

    At any moment: any number of shared references, or exactly one mutable reference, never both.

  2. Question 2 of 2 Why does rustc refuse push_conflict.rs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The push may move the items to a new block, while first still points into the old one

    The rule is checked without running the program: a mutable borrow for the push while the shared borrow in first is still to be used is refused, whatever the capacity happens to be.

A borrow lasts until its last use, not until the end of the block

A borrow lasts from the line that makes the reference to the last line that uses it. So moving one line can fix E0502:

last_use.rs: the same push, after the last use of first Rust · last_use.rs
// A borrow lasts until its last use, so the order of the lines decides what compiles.
fn main() {
    let mut queue: Vec<u64> = Vec::with_capacity(4);
    queue.extend([10, 20, 30, 40]);

    let first = &queue[0];
    println!("first is {first}"); // the last use of first: the borrow ends here
    queue.push(50); // so the Vec may change now

    let copied = queue[0]; // a u64 is Copy: copied holds its own value, no borrow
    queue.push(60);
    println!("copied is {copied}, queue is {queue:?}");
}

Output

first is 10
copied is 10, queue is [10, 20, 30, 40, 50, 60]

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O last_use.rs && ./last_use

Printing first before the push ends its borrow, so the push is allowed. Copying a value out of the Vec avoids the borrow altogether: a u64 is Copy, so copied holds its own 10 and points into nothing. Step through which borrow is in use at each line:

Interactive · stage by stage Which borrow is in use at each line of last_use.rs Step through the six statements. Watch where the shared borrow in first ends, and why both pushes are allowed.
Which borrow is in use at each line of last_use.rs, step 6 of 6. Showing let first = &queue[0] (first borrows queue), println! of first (printed 10; borrow over), queue.push(50) (allowed: no reader left), let copied = queue[0] (copied is 10, owns it), queue.push(60) (allowed), println! at the end (10 and six items). In focus: println! at the end.let first = &queue[0]first borrows queueprintln! of firstprinted 10; borrow overqueue.push(50)allowed: no reader leftlet copied = queue[0]copied is 10, owns itqueue.push(60)allowedprintln! at the end10 and six items

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. A borrow starts. first borrows the first item of queue. From here until its last use, nothing may change queue.
  2. Its last use. The println! reads first for the last time and prints first is 10. The borrow ends here, not at the end of main.
  3. The push is allowed. queue.push(50) borrows queue mutably. No shared borrow is in use any more, so rustc allows it, even if the items move to a new block.
  4. A copy, not a borrow. let copied = queue[0]; copies the u64 out of the Vec. copied owns its own 10 and borrows nothing.
  5. The second push. queue.push(60) is a mutable borrow again, and it is allowed: copied does not point into the Vec, so the Vec may grow or move.
  6. Read at the end. The last println! reads copied and the whole queue, which now holds 10, 20, 30, 40, 50 and 60.

This is called non-lexical lifetimes: the length of a borrow follows the flow of the code, not the braces of the block. It arrived with the 2018 edition, and Rust 1.63 removed the older borrow checker from every edition.

Checkpoint

Check: Where a borrow ends

  1. Question 1 of 2 queue is a Vec declared with mut. Which order of the three lines compiles?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: let first = &queue[0]; println!("{first}"); queue.push(30);

    Moving the last use of a reference before the change is often the whole fix, as in last_use.rs.

  2. Question 2 of 2 let copied = queue[0]; queue.push(60); println!("{copied}"); compiles. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: copied holds its own u64, copied out of the Vec, so no borrow is in use during the push

    Copying a small value out of a collection is the other common fix: the copy has no borrow to conflict with.

Two writers, a missing mut and two-phase borrows, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Three more cases come from the same rules. This part reproduces E0499 and E0596 with their fixes, and shows why v.push(v.len()) compiles while the same call written out by hand does not.

In this part:

  • Two mutable borrows of one Vec at once: E0499
  • Changing a value needs a binding declared with mut: E0596
  • Why v.push(v.len()) compiles: two-phase borrows
  • A &mut passed to a function is lent on, not given away
  • The errors of this lesson in one table

6 runnable examples (Rust) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

What the rules prevent: invalidated references and data races Pro: opens with a Pro, Premium or Ultimate pass

The two rules stop two bugs that C++ compiles without a word: a reference into a vector that a push has moved, and two threads that write one value at once. This part shows Rust refusing each, with the fix for each, then gives a stock program to repair without cloning a collection.

In this part:

  • Pushing onto a Vec while a loop reads it
  • Why such a bug hides in tests: which pushes can move the items
  • Two threads writing one Vec, refused at compile time
  • Exercise: fix a stock program without cloning a collection

7 runnable examples (Rust) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: references and the borrowing rules

These questions are about lending with & and &mut, the rule of many readers or one writer, and where a borrow ends. Every choice says why it is right or what it misses.

Practice that adapts to you

Practise this lesson

9 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 Who owns marks while total(&marks) runs?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: main, which lent it

    Lending is the opposite of moving: the value stays where it was, and the borrower gets an address it may use for a while.

  2. Question 2 of 9 What does rustc say about this function?

    Read the code, then choose one answer.

    fn shout(s: &String) {
        s.push_str("!");
    }
    Show the answer: Question 2 of 9

    Answer: It refuses it, because push_str needs to change the String and s only lends it to read

    The fix is fn shout(s: &mut String), called as shout(&mut name) on a name declared with let mut. rustc 1.99.0 refuses the version with &String.

  3. Question 3 of 9 Why may any number of shared references to one value be in use at the same time?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: None of them can change the value, so no reader can disturb another

    Trouble needs a writer. With only readers, the value stays where it is and as it is for as long as they need.

  4. Question 4 of 9 Which statement is the borrowing rule?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: Any number of shared references or one mutable reference, never both at once

    Together with "references must always be valid", this rule is what every borrow error of the module enforces.

  5. Question 5 of 9 What happens with this code?

    Read the code, then choose one answer.

    let mut names = vec![String::from("Asha")];
    let first = &names[0];
    names.clear();
    println!("{first}");
    Show the answer: Question 5 of 9

    Answer: E0502, because clear needs a mutable borrow while first is still to be used

    Any method that changes a Vec, push, clear, remove or sort, conflicts with a reference into it that is still to be used.

  6. Question 6 of 9 A push can move the items only when the Vec is full. Why does rustc refuse every push while a reference into the Vec is in use?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Whether a push needs a bigger block depends on the length at run time, which the compiler does not know

    A rule that holds before the program runs has to cover the worst case, here a push that needs a bigger block.

  7. Question 7 of 9 When does the borrow made by let first = &queue[0]; end?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: At the last line that uses first

    A borrow lasts from the reference's creation to its last use, not to the closing brace.

  8. Question 8 of 9 Does this compile, and if so what does it print?

    Read the code, then choose one answer.

    let mut queue: Vec<u64> = vec![10, 20];
    let total: u64 = queue.iter().sum();
    queue.push(total);
    println!("{queue:?}");
    Show the answer: Question 8 of 9

    Answer: It prints [10, 20, 30]

    Rust 1.99.0 printed [10, 20, 30]. Working a value out first and changing the collection afterwards is the shape that always compiles.

  9. Question 9 of 9 A function holds let longest = names.iter().max().unwrap(); then calls names.push(…), then prints longest. What is the smallest fix?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: Use longest before the push, or copy out what you need from it first

    Start with the order of the lines: most borrow errors in a single function are fixed by finishing with a reference before the change.

Interview questions

Warm-up (fresher): what is the difference between &T and &mut T?

&T is a shared reference: it lends a value to read, any number of them can be in use at once, and none of them can change it, so a &T is Copy. &mut T is a mutable reference: it lends the value to be changed in place, needs an owner declared with mut, and while it is in use no other reference to the value may be used, so it is not Copy. Both are borrows: the owner keeps the value, and the compiler checks that no reference outlives it. A good answer adds why: many readers or one writer is what lets the compiler rule out data races and references into moved memory.

  1. Fresher to mid

    Why does Rust refuse let first = &v[0]; v.push(4); println!("{first}"); when the same three lines compile in C++? What could go wrong at run time, and how would you fix the Rust code?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid

    A game keeps players: Vec<Player> and must move points from player i to player j in one step. let a = &mut players[i]; let b = &mut players[j]; is refused. What are your options, and what does each do when i == j?

    Model answer, Pro. First step and follow-ups

  3. Mid to senior

    Explain how the compile-time rule of many readers or one writer rules out data races between threads. Which concurrency bugs can a safe Rust program still have?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A reference lends a value without moving it, &T to read and &mut T to change it, and the owner keeps the value throughout.
  • At any moment a value may have any number of shared references or one mutable reference, never both, and rustc refuses a program that breaks the rule (E0502).
  • A push may move a Vec’s items to a new block, which is why a reference into a Vec and a change to it cannot overlap.
  • A borrow lasts until the reference’s last use, so moving a line or copying a value out often fixes a borrow error.

Terms in this lesson

borrow
To take a reference to a value, so that other code can use the value while its owner keeps it. A borrow lasts from where the reference is made to the last place it is used.
borrow checker
The part of the Rust compiler that checks every reference against the ownership and borrowing rules, and refuses to build a program in which a reference could outlive its value or break those rules.
mutable reference
A reference written &mut T, through which code can change the value it refers to. While it is in use, no other reference to that value may be used.
reference
A value that refers to another value without owning it: &T lets code read the value and &mut T lets it change it. The compiler checks that no reference outlives the value it refers to.
shared reference
A reference written &T. Any number of them may refer to a value at the same time, and none of them can change it.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.