Beginner · Borrowing, slices and lifetimes · topic 14 of 50 · 30 min
References and borrowing
Explanation
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.
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
} 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 heapMutable 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.
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
}The borrowing rules
- At any given time, you can have EITHER any number of shared references (&T) OR exactly one mutable reference (&mut T), but not both.
- 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 borrowed | Yes | Yes |
| One or more &T | Yes | No |
| One &mut T | No | No |
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.
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
}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
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.
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}");
}Practice (0/3 done)
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}"); }
Which fix makes this compile? fn greet(name: &String) { println!("Hi {name}"); } fn main() { let n = String::from("Om"); greet(n); println!("{n}"); }
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
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- The borrow checker in practiceBorrowing, slices and lifetimes · same module
- Slices and stringsBorrowing, slices and lifetimes · same module
- LifetimesBorrowing, slices and lifetimes · same module
- Rc, RefCell and WeakClosures, iterators and smart pointers
- Interview questions: ownership, borrowing and lifetimesRust careers and interviews
- References: another name for a variableC++ Programming (modern C++ and DSA) · References, pointers, arrays and strings
Questions people ask about this
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.
Which type should you use for shared ownership across threads?Related
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.
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
- 3 more topics in “Borrowing, slices and lifetimes” before its exam: The borrow checker in practice, Slices and strings, Lifetimes.
- Module 4 exam: Borrowing, slices and lifetimes · 13 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Structs, enums and pattern matching. 50 topics left in the course before the final exam.