References & Borrowing
In the last lesson, passing a String to a function moved it, and getting it back meant returning it in a tuple. That is tiring. Most of the time a function only needs to use a value for a while, not own it. Rust lets you lend a value with a reference, and lending is called borrowing.
In this lesson you will learn shared and mutable references, the two borrowing rules the compiler enforces, and why those rules make whole classes of bugs impossible.
Shared references with &
A reference is like an address that points to a value owned by someone else. You create one with &. The owner keeps ownership, so nothing is moved and nothing is dropped when the reference goes away.
fn letter_count(word: &String) -> usize {
word.len()
}
fn main() {
let name = String::from("Kabir");
let n = letter_count(&name);
println!("{name} has {n} letters");
}Kabir has 5 letters
&name lends name to the function, and the parameter type &String says "a reference to a String". After the call, name is still valid in main. Compare this with count_letters in Ownership & Moves, which had to hand the String back.
References are immutable by default: through word the function can read the text but not change it. (In real code you would write the parameter as &str instead of &String, because it accepts more kinds of text; you will see why in Slices.)
Mutable references with &mut
To let a function change a borrowed value, lend it with &mut. The variable itself must be declared mut too.
fn add_topping(order: &mut String, extra: &str) {
order.push_str(" + ");
order.push_str(extra);
}
fn add_points(points: &mut u32, bonus: u32) {
*points += bonus;
}
fn main() {
let mut order = String::from("Paneer pizza");
add_topping(&mut order, "extra cheese");
add_topping(&mut order, "olives");
println!("{order}");
let mut points = 120;
add_points(&mut points, 30);
println!("Reward points: {points}");
}Paneer pizza + extra cheese + olives Reward points: 150
In add_points, points is a reference, not a number, so you write *points to reach the value it points to. The * is the dereference operator. Method calls such as order.push_str(...) dereference automatically, which is why add_topping needs no *.
The borrowing rules
At any moment, for a given value, you can have either:
- any number of shared references (
&T), or - exactly one mutable reference (
&mut T),
but never both at the same time. And every reference must always point to a valid value.
| You want to | Allowed at the same time |
|---|---|
Read through several & references | Yes, as many as you like |
Change through one &mut reference | Only if no other reference is in use |
Read through & while someone changes through &mut | Never |
Here is the rule being broken with two mutable borrows:
fn main() {
let mut notes = String::from("Buy milk");
let a = &mut notes;
let b = &mut notes;
a.push_str(", pay fees");
b.push_str(", call Rohan");
}error[E0499]: cannot borrow `notes` as mutable more than once at a time
--> src/main.rs:4:13
|
3 | let a = &mut notes;
| ---------- first mutable borrow occurs here
4 | let b = &mut notes;
| ^^^^^^^^^^ second mutable borrow occurs here
5 | a.push_str(", pay fees");
| - first borrow later used hereWhy so strict? When two parts of a program can change the same data at once, or one part reads while another changes it, you get a data race: results that depend on timing and break in ways that are almost impossible to reproduce. In a single thread the same pattern causes bugs like the one in the Rust Introduction, where a vector moved its items while a reference still pointed at the old place. Rust rejects these programs before they run.
Borrows end at their last use
A borrow does not last until the end of the block. It ends after the last place the reference is used. The compiler feature that works this out is called non-lexical lifetimes (NLL). It makes many sensible programs compile:
fn main() {
let mut marks = vec![67, 74, 81];
let first = &marks[0];
println!("First mark: {first}"); // last use of `first`
marks.push(90); // fine: the shared borrow has ended
println!("{marks:?}");
}First mark: 67 [67, 74, 81, 90]
If you moved the println! of first below marks.push(90), the shared borrow would still be alive during the push, and the program would fail with error E0502.
No dangling references
A dangling reference points at memory that has already been freed. Rust never lets one exist. Look at this function, which tries to return a reference to a local String:
fn make_greeting() -> &String {
let text = String::from("Namaste");
&text
} // `text` is dropped here, so the reference would point at freed memoryThe compiler stops with error[E0106]: missing lifetime specifier and explains that the return type contains a borrowed value, "but there is no value for it to be borrowed from". The fix is to return the owned value, so ownership moves out to the caller:
fn make_greeting() -> String {
String::from("Namaste")
}You will learn exactly how the compiler tracks how long references live in Lifetimes.
Common mistakes
- Forgetting
muton the variable.&mut orderneedslet mut order. - Forgetting
*on simple values.points += 30does not work whenpointsis&mut u32; write*points += 30. - Holding a reference too long. Keep borrows short, and finish reading through a
&before you change the value. - Returning a reference to a local variable. Return the owned value (
String,Vec) instead.
Key points
&valuecreates a shared (read-only) reference;&mut valuecreates a mutable one. Borrowing never moves ownership.- You may have many shared references or one mutable reference to a value at a time, never both.
- These rules prevent data races and invalid references, and they are checked at compile time.
- A borrow ends after its last use (non-lexical lifetimes), not at the end of the block.
- Use
*to reach the value behind a reference; method calls dereference automatically.
Exercise
Write a function apply_discount(price: &mut u32, percent: u32) that lowers a price in rupees by the given percentage (use integer maths). In main, create a mutable price of 1,500 rupees, apply a 10% discount and then a further 5% discount, and print the price after each step. Then write fn describe(price: &u32) that only prints the price, and call it at the end.
Show solution
fn apply_discount(price: &mut u32, percent: u32) {
*price -= *price * percent / 100;
}
fn describe(price: &u32) {
println!("Final price: Rs. {price}");
}
fn main() {
let mut price = 1500;
apply_discount(&mut price, 10);
println!("After 10%: Rs. {price}");
apply_discount(&mut price, 5);
println!("After 5% more: Rs. {price}");
describe(&price);
}After 10%: Rs. 1350 After 5% more: Rs. 1283 Final price: Rs. 1283
The first discount is 150, leaving 1350. Five percent of 1350 is 67.5, which integer division makes 67, leaving 1283. Each mutable borrow ends when its function call returns, so the shared borrow for describe is allowed.
Quick Quiz
1. What does & do in letter_count(&name)?
A reference borrows the value; ownership stays with name.
2. Which combination of references to one value is allowed at the same moment?
You can have many shared references or one mutable reference, but not a mix.
3. When does a borrow end in modern Rust?
Non-lexical lifetimes end a borrow at its last use.
4. How do you add 5 to the number behind n: &mut i32?
Dereference with * to change the value the reference points to.
Learn Rust with a mentor
Free lessons get you started. Our Programming & Software Development training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
