Skip to content

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.

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: 5
Still mine: hello
▶ Try it Yourself

The &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.

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!!!
▶ Try it Yourself

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.

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 immutable

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: hello
hello, world
▶ Try it Yourself

The 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.

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.

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.

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 main
▶ Try it Yourself

The 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.

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
- apples
▶ Try it Yourself

add_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.

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 here
4 | let second = &mut message;
| ^^^^^^^^^^^^ second mutable borrow occurs here
5 | first.push_str(" 10");
| ----- first borrow later used here

Hint: 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 points
▶ Try it Yourself

In 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 &mut to 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

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