Skip to content

Beginner · Borrowing, slices and lifetimes · topic 14 of 50 · 30 min

References and borrowing

Explanation

A reference (&value) lets code use a value without owning it; this is called borrowing. A shared reference &T can only read; a mutable reference &mut T can change the value. The rule: at any moment you may have many shared references OR exactly one mutable reference, and a reference can never outlive the value it points to.

The problem from the last module

Passing a String to a function moved it, so we had to pass it back. Rust's answer is the reference. A reference is like a pointer: it holds the address of a value owned by someone else. Unlike a raw pointer, Rust guarantees a reference always points to a valid value. Creating a reference is called borrowing, because you use the value for a while and the owner keeps it.

rust
fn length(s: &String) -> usize {
    s.len()
}

fn main() {
    let city = String::from("Bhopal");
    let n = length(&city);          // lend city to the function
    println!("{city} has {n} bytes"); // city is still ours
}
&city creates a reference. The parameter type &String says "a borrowed String".
output
   main's stack            length's stack           HEAP
  city: ptr ---------------------------------->  B h o p a l
        len 6                 s: ptr --+
        cap 6                          |
     ^                                 |
     +---------------------------------+   s points to city, not to the heap
The reference s points at the owner city. When length ends, only s disappears; nothing is dropped.
Think of it like this: Borrowing is like lending your Aadhaar card for a photocopy. The shopkeeper uses it for a moment and gives it back; you are still the owner. He cannot keep it, sell it, or throw it away, and you cannot tear it up while he is holding it.

Mutable references

A plain reference &T is a shared reference: you can read through it but not change the value. To let a function change a borrowed value, the owner must be mut and you pass &mut.

rust
fn add_surname(name: &mut String) {
    name.push_str(" Sharma");
}

fn main() {
    let mut name = String::from("Rohit");
    add_surname(&mut name);
    println!("{name}");   // Rohit Sharma
}
Three places say mut: the variable, the reference &mut name, and the parameter type &mut String.

The borrowing rules

  1. At any given time, you can have EITHER any number of shared references (&T) OR exactly one mutable reference (&mut T), but not both.
  2. References must always be valid: a reference can never outlive the value it points to.

Why rule 1? If one part of the code is changing a value while another part is reading it, the reader can see half-changed data. When this happens between threads it is called a data race, one of the hardest bugs to find. Rust rules it out at compile time, even in single-threaded code, where the same pattern causes bugs such as a list being changed while it is being looped over.

You have...Can you add a &T?Can you add a &mut T?
Nothing borrowedYesYes
One or more &TYesNo
One &mut TNoNo
Think of it like this: Think of a shared Google Doc in "view only" mode: many people can read at once. When one person takes "edit" mode under this rule, everyone else must close it until they finish. Readers never see the text change under their eyes.

Dereferencing with *

To reach the value behind a reference, you sometimes write * in front of it. This is called dereferencing. Method calls and println! do it for you automatically, so you mostly need * when changing a number through a &mut.

rust
fn add_bonus(points: &mut u32) {
    *points += 50;     // change the value behind the reference
}

fn main() {
    let mut points = 100;
    add_bonus(&mut points);
    println!("{points}");   // 150
}
Without the *, points += 50 would try to add to the reference itself, which does not compile.

Common mistakes

  • Passing name instead of &name to a function that takes &String: "expected &String, found String". Add the &.
  • Calling a &mut function on a variable that is not mut: "cannot borrow as mutable". Declare it with let mut.
  • Forgetting * when changing a number through &mut. The compiler suggests it.
  • Taking &mut when you only read. Use & so others can read at the same time.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

References and borrowing · 1/7

&value = borrow

Moving values in and out of functions is clumsy. References solve it. A reference lets you use a value without owning it.

Prefer a video by a teacher? Find free videos on “rust references and borrowing explained mutable references” on YouTube ↗

In real life: Updating a shopping cart total

In a shopping app, a function that shows the cart only needs to read it, so it takes &Vec<Item>. A function that applies a coupon must change the total, so it takes &mut. The cart itself stays owned by the checkout screen the whole time; nothing is moved or copied.

rust
fn show(cart: &Vec<u32>) {
    println!("{} items, total Rs {}", cart.len(), cart.iter().sum::<u32>());
}

fn apply_coupon(total: &mut u32, off: u32) {
    *total = total.saturating_sub(off);
}

fn main() {
    let cart = vec![499, 1299, 250];
    show(&cart);
    let mut total: u32 = cart.iter().sum();
    apply_coupon(&mut total, 200);
    println!("To pay: Rs {total}");
}
Prints: 3 items, total Rs 2048, then To pay: Rs 1848

Practice (0/3 done)

Task 1 · Question

What does this print? fn add_one(n: &mut i32) { *n += 1; } fn main() { let mut x = 5; add_one(&mut x); add_one(&mut x); println!("{x}"); }

Task 2 · Question

Which fix makes this compile? fn greet(name: &String) { println!("Hi {name}"); } fn main() { let n = String::from("Om"); greet(n); println!("{n}"); }

Task 3 · Practice

Write fn total(marks: &[u32]) -> u32 (or &Vec<u32>) that adds up marks, and fn give_grace(marks: &mut Vec<u32>) that adds 5 to every mark below 35. In main, call total, then give_grace, then total again, and print both totals.

Quick quiz

Question 1

1.What is borrowing?

Question 2

2.Which combination is allowed at the same time?

Question 3

3.What does * do in *n += 1 when n is &mut i32?

Question 4

4.Why does Rust forbid a &mut while & references exist?

Question 5

5.What is needed to call add(&mut s)?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

What is borrowing?

Answer: Using a value through a reference without taking ownership

A reference borrows: the owner keeps the value.

Which combination is allowed at the same time?

Answer: Three & to the same value

Any number of shared references is fine, as long as there is no mutable one.

What does * do in *n += 1 when n is &mut i32?

Answer: Reaches the value behind the reference

* dereferences: it accesses the value the reference points to.

Why does Rust forbid a &mut while & references exist?

Answer: So readers never see the value change under them (no data races)

Exclusive access for writers prevents data races and similar bugs.

What is needed to call add(&mut s)?

Answer: s must be declared with let mut

You can only borrow mutably from a mutable variable.

Until when is a shared borrow alive under non-lexical lifetimes?Related

Answer: Until its last use

A borrow lasts from creation to its last use.

From The borrow checker in practice

Which type should you use for shared ownership across threads?Related

Answer: Arc

Arc uses atomic counting, so it is thread-safe.

From Rc, RefCell and Weak

What is the best fix for "returns a reference to data owned by the current function"?Related

Answer: Return an owned value

Move ownership out instead of borrowing a local.

From Interview questions: ownership, borrowing and lifetimes

Which line does NOT compile?Related

Answer: int& r;

A reference must be bound to something when it is created.

From References: another name for a variable

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 3 more topics in “Borrowing, slices and lifetimes” before its exam: The borrow checker in practice, Slices and strings, Lifetimes.
  2. Module 4 exam: Borrowing, slices and lifetimes · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
  3. Then the next module, Structs, enums and pattern matching. 50 topics left in the course before the final exam.

← Previous: Ownership and functions