Rust Borrowing
On the ownership page you saw a problem: passing a String to a function moves it, so you can’t use it afterward. Constantly moving values around (or cloning them) would be painful. Borrowing is Rust’s solution.
Borrowing means letting some code use a value without taking ownership of it — like lending a friend a book. They can read it, then give it back, and you still own it.
References: borrowing with &
Section titled “References: borrowing with &”You create a reference by putting & in front of a value. A reference lets a function look at the data without owning it:
fn print_length(s: &String) { // borrows s, doesn't own it println!("Length: {}", s.len());}
fn main() { let text = String::from("hello"); print_length(&text); // lend text to the function println!("Still mine: {text}"); // ✅ text is still valid here}Output:
Length: 5Still mine: helloThe &text says “let this function borrow text.” Because the function only borrowed it, ownership stays with main, and you can keep using text afterward. Compare this to the ownership page, where the same code without & caused an error.
Mutable references: borrowing to change
Section titled “Mutable references: borrowing to change”A plain & reference is read-only — you can look but not touch. If a function needs to change the borrowed value, use a mutable reference with &mut:
fn add_excitement(s: &mut String) { s.push_str("!!!");}
fn main() { let mut text = String::from("hello"); add_excitement(&mut text); // lend it mutably println!("{text}"); // hello!!!}Output:
hello!!!Notice three things had to line up: text is declared mut, the function asks for &mut String, and the call passes &mut text. All three are required to change a borrowed value.
The borrowing rules
Section titled “The borrowing rules”Rust enforces two rules about references. They exist to prevent data races — a whole class of bugs where two pieces of code touch the same data at the same time and corrupt it.
At any given time, you can have either:
- any number of read-only references (
&), or - exactly one mutable reference (
&mut),
but not both at once.
In plain English: many readers or one writer, never both together. This code breaks the rule:
fn main() { let mut text = String::from("hello");
let r1 = &text; // a read-only borrow let r2 = &mut text; // ❌ can't borrow mutably while r1 exists
println!("{r1} {r2}");}The compiler stops you:
error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutableWhen does a borrow end?
Section titled “When does a borrow end?”You might expect a borrow to last until the closing } of its scope. It doesn’t. A borrow lasts only until the last place the reference is used. After that, the value is free to be borrowed again, even mutably.
That means the broken example above can be fixed just by finishing with r1 before you create r2:
fn main() { let mut text = String::from("hello");
let r1 = &text; println!("Reading: {r1}"); // last use of r1, so its borrow ends here
let r2 = &mut text; // ✅ fine: nobody is reading anymore r2.push_str(", world");
println!("{text}");}Output:
Reading: hellohello, worldThe rule is still “many readers or one writer”. It’s just checked over the stretch of code where each reference is actually used. The reader r1 is finished by the time the writer r2 appears, and r2 is finished before the last println! reads text again. Nothing overlaps, so Rust is happy.
This is the most common way to fix a borrow error: change the order. Do all the reading you need, then the writing (or the other way around), instead of mixing them.
Why this matters
Section titled “Why this matters”These rules are checked entirely at compile time, so they cost nothing when your program runs. This is how Rust can promise “no data races” and still be as fast as C — the safety is proven before the program ever starts.
Practice
Section titled “Practice”Try each one yourself first, in the Rust Playground or a local project. If the compiler complains, read the error: it usually shows exactly where the borrows overlap.
1. Count without taking
Section titled “1. Count without taking”Write a function total_letters that borrows a Vec<String> and returns how many letters all the words have together. After calling it, main should still be able to print the vector.
Hint: the parameter type is &Vec<String>, and you call it with &words. Loop over the borrowed vector with for word in list and add up word.len().
Show solution
fn total_letters(list: &Vec<String>) -> usize { let mut total = 0; for word in list { total += word.len(); } total}
fn main() { let words = vec![String::from("borrow"), String::from("check")]; let count = total_letters(&words); // lend the vector println!("{count} letters"); println!("{:?} still belongs to main", words);}Output:
11 letters["borrow", "check"] still belongs to mainThe function only needs to look at the words, so a read-only & borrow is enough. If you wrote list: Vec<String> instead, the vector would move into the function and the last println! wouldn’t compile.
2. A shopping list
Section titled “2. A shopping list”Write two functions: add_item, which adds an item to a shopping list, and print_list, which prints every item. Add three items, then print the list.
Hint: one function changes the list and one only reads it. Which one needs &mut Vec<String>, and which one is fine with &Vec<String>? To turn a &str into a String, use String::from(item).
Show solution
fn add_item(list: &mut Vec<String>, item: &str) { list.push(String::from(item));}
fn print_list(list: &Vec<String>) { for item in list { println!("- {item}"); }}
fn main() { let mut list = Vec::new(); add_item(&mut list, "milk"); add_item(&mut list, "bread"); add_item(&mut list, "apples"); print_list(&list);}Output:
- milk- bread- applesadd_item changes the list, so it gets a &mut borrow, and list in main has to be mut. print_list only reads, so a plain & is enough. Each &mut list borrow ends when its call returns, which is why the next call can borrow it again.
3. Two writers at once
Section titled “3. Two writers at once”This program doesn’t compile. Fix it so it prints Score: 10 points, keeping both first and second.
fn main() { let mut message = String::from("Score:"); let first = &mut message; let second = &mut message; first.push_str(" 10"); second.push_str(" points"); println!("{message}");}error[E0499]: cannot borrow `message` as mutable more than once at a time --> src/main.rs:4:18 |3 | let first = &mut message; | ------------ first mutable borrow occurs here4 | let second = &mut message; | ^^^^^^^^^^^^ second mutable borrow occurs here5 | first.push_str(" 10"); | ----- first borrow later used hereHint: only one writer at a time. Look at When does a borrow end? above: can you finish using first before second is created?
Show solution
fn main() { let mut message = String::from("Score:");
let first = &mut message; first.push_str(" 10"); // last use of first: its borrow ends here
let second = &mut message; second.push_str(" points");
println!("{message}");}Output:
Score: 10 pointsIn the original, first was still going to be used (on line 5) when second was created on line 4. That’s two writers at the same time. Moving first.push_str up means first is done before second exists, so there is only ever one &mut at a time.
- Borrowing (
&) lets code use a value without taking ownership — you get it back. - A plain
&reference is read-only; use&mutto modify the borrowed value. - To mutate through a reference, the variable, the parameter, and the call all need
mut/&mut. - The rule: many readers or one writer, never both at once.
- A borrow ends at the reference’s last use, so reordering your code often fixes a borrow error.
- All of this is checked at compile time, so it’s free at runtime.
Quick check
Section titled “Quick check”Quick check
1. What does putting & in front of a value do when passing it to a function?
2. According to the borrowing rules, which combination is allowed at the same time?
3. What do you need to modify a value through a reference?
4. Why can Rust enforce the borrowing rules without any runtime performance cost?
Score: 0 / 4
Read more
Section titled “Read more”- References and Borrowing — The Rust Book: the official chapter, including dangling references and why Rust refuses to create them.
- Borrowing — Rust by Example: runnable examples of
&borrows, with follow-up pages on mutable borrows and aliasing. - Non-lexical lifetimes — The Edition Guide: the change that made borrows end at their last use instead of at the closing brace.
- The Slice Type — The Rust Book: borrowing just part of a string or array, the natural next step after references.
reference— standard library docs: what&Tand&mut Tare, and which traits they implement.