Borrowing
Intermediate · Ownership
What & why
The last lesson ended on a frustrating note: passing a String into a function gives it away, and
then you can’t use it anymore. Borrowing is the fix. It lets you lend a value to a function so
it can look at it (or even change it) and then hand it back — no giving away, no copying, no cost.
This is the “lend without giving away” idea from the Ownership lesson, made real.
The idea, slowly
The problem borrowing solves
Remember this from the last lesson? Passing s into a function moved it, and the line after broke:
fn main() {
let s = String::from("hello");
greet(s); // s is MOVED into greet
// println!("{}", s.len()); // ERROR: s was given away
}
fn greet(word: String) {
println!("Hi, {}!", word);
}
That’s a lot of ceremony just to look at a string. In real life, if a friend wants to read your
book, you don’t sign the book over to them forever — you lend it, they read it, they give it
back. Rust has exactly that: a reference, written with an ampersand &.
Lending with &
A reference is a way to say “let this function use my value without taking ownership of it.” You
create one by putting & in front of the value, and the function says it wants one by putting &
in front of the type:
fn main() {
let s = String::from("hello");
greet(&s); // lend s (don't give it away)
println!("the word was {} letters", s.len()); // s is STILL OURS — works!
}
fn greet(word: &String) { // "word" is a reference, not owned
println!("Hi, {}!", word);
}
Run this. It prints the greeting and the length. Nothing moved. That’s the whole point of
borrowing: &s hands the function a reference to the string, s keeps ownership, and after
greet finishes you can keep using s normally.
The act of making and using a reference is called borrowing. You “borrow” the value, the same way your friend borrows the book. And just like a borrowed book, there are rules about what you’re allowed to do with something you don’t own.
What the compiler is thinking
When the function takes &String, the compiler thinks: “This function is only borrowing. It does
not own this string, so when the function ends, it must NOT free the memory — the real owner back
in main is still using it.” When the function takes a plain String, the compiler thinks the
opposite: “This function now owns it; when the function ends, drop it.” That one little & is
what tells Rust which of those two stories is true.
Read-only by default: &
A plain & borrow is read-only. You can look, but you can’t change:
fn main() {
let s = String::from("hello");
let len = measure(&s);
println!("{} is {} chars", s, len);
}
fn measure(word: &String) -> usize {
word.len() // reading is fine
// word.push('!'); // ERROR: can't change a value you only borrowed read-only
}
This is like borrowing a library book: you may read it, but you may not scribble in it. If you try
to change it, the compiler stops you with cannot borrow ... as mutable.
When you DO want to change it: &mut
Sometimes you want the function to change your value — say, add an exclamation mark. For that you
need a mutable borrow, written &mut. Three things all have to line up:
- The original variable must be declared
mut(it has to be changeable in the first place). - You pass it with
&mut. - The function accepts
&mut.
fn main() {
let mut s = String::from("hello"); // 1. must be mut
add_excitement(&mut s); // 2. lend it mutably
println!("{}", s); // prints: hello!
}
fn add_excitement(word: &mut String) { // 3. accepts &mut
word.push('!'); // now changing it is allowed
}
The value is still owned by main the whole time. We only lent the right to change it for the
duration of the call, then took it back. This is like lending your friend a pencil-and-paper form
and saying “go ahead, fill it in” — they modify your thing, but it’s still yours.
The one big rule: one writer, or many readers
Here’s the rule that trips everyone up, so read it slowly. At any given moment, for one value, you can have either:
- any number of read-only (
&) borrows — many readers are fine, OR - exactly one mutable (
&mut) borrow — one writer, and nobody else.
You can never have a &mut at the same time as any other borrow. Why? Imagine one part of your
code is reading a list while another part is deleting items from it — the reader would see garbage.
Rust forbids that situation at compile time so it can never happen while the program runs.
fn main() {
let mut s = String::from("hello");
let r1 = &s; // reader 1
let r2 = &s; // reader 2 — fine, many readers allowed
println!("{} and {}", r1, r2); // last use of r1 and r2
let w = &mut s; // now a writer — allowed, because r1/r2 are done being used
w.push('!');
println!("{}", w);
}
Think of it as a shared document: lots of people can read it at the same time, but the moment someone wants to edit, everyone else has to step away. Rust enforces this so your data can never change underneath you while you’re looking at it.
Common mistakes
- Forgetting
&on both sides. If the value is&sbut the function still saysword: String, or vice versa, the types don’t match and you getmismatched types: expected String, found &String. The&has to be on the value and on the parameter type. - Trying to mutate through a plain
&borrow. A read-only borrow can’t call methods that change the value (like.push). The error iscannot borrow ... as mutable, as it is behind a&reference. Fix: use&muteverywhere and make the original variablemut. - A
&mutwhile another borrow is alive.cannot borrow ... as mutable because it is also borrowed as immutablemeans you still have a reader hanging around. The fix is usually to stop using the earlier reference before you start the mutable one. - Forgetting
muton the variable itself. You can’t take a&mutof something that was never declaredmut. The error points you back to theletand says to addmut.
More examples
Reading a cart’s size without taking it
A dashboard needs to show how many items are in the cart without taking the cart away from the checkout logic that still needs it.
fn summarize(items: &Vec<String>) -> usize {
items.len()
}
fn main() {
let cart = vec![String::from("pen"), String::from("notebook")];
println!("{} items in cart", summarize(&cart));
println!("still have it: {:?}", cart);
}
Restocking a shelf through a mutable borrow
A warehouse restock function needs to add new items to an existing inventory list without taking ownership of the whole warehouse.
fn restock(inventory: &mut Vec<&str>) {
inventory.push("stapler");
}
fn main() {
let mut inventory = vec!["paper", "pens"];
restock(&mut inventory);
println!("{:?}", inventory);
}
Doubling every score in place
A game engine wants to apply a 2x multiplier to every player’s score after a bonus round, editing the list it was given rather than building a new one.
fn double_all(scores: &mut Vec<i32>) {
for score in scores.iter_mut() {
*score *= 2;
}
}
fn main() {
let mut scores = vec![10, 20, 30];
double_all(&mut scores);
println!("{:?}", scores);
}
Why two &mut borrows can’t coexist
Imagine two parts of a program both trying to hand out edit access to the same balance at once — Rust catches that at compile time before it becomes a real bug.
fn main() {
let mut balance = 100;
let r1 = &mut balance;
let r2 = &mut balance; // ERROR: second mutable borrow while r1 is still alive
println!("{} {}", r1, r2);
}
Borrowing one field while another stays free
A player struct has a name (read for the scoreboard) and a score (updated after each round). Rust lets you borrow each field independently at the same time, since they don’t overlap.
struct Player {
name: String,
score: u32,
}
fn main() {
let mut player = Player { name: String::from("Kai"), score: 0 };
let name_ref = &player.name; // borrow just the name field
let score_ref = &mut player.score; // borrow just the score field, mutably
*score_ref += 10;
println!("{name_ref} now has {score_ref} points");
}
Your turn
This program wants to add a "." to the end of the sentence, then print it. It doesn’t compile.
Fix it so it prints learning rust. (Hint: three things have to line up for a mutable borrow.)
fn main() {
let sentence = String::from("learning rust");
finish(sentence);
println!("{}", sentence);
}
fn finish(text: &String) {
text.push('.');
}
Show solution
Two problems: the function takes ownership (plain String) but we need it back, and it tries to
change a read-only borrow. Switch everything to a mutable borrow and make sentence mutable:
fn main() {
let mut sentence = String::from("learning rust"); // must be mut
finish(&mut sentence); // lend it mutably
println!("{}", sentence); // still ours — prints: learning rust.
}
fn finish(text: &mut String) { // accept a mutable borrow
text.push('.'); // now allowed to change it
}
The &mut lets finish change the string in place, and because it only borrowed, sentence
still belongs to main afterward.
Quick check
Remember this
- A reference (
&) lets a function use a value without taking ownership — it borrows, then gives it back. - Plain
&is read-only;&mutlets you change the value (and needs the original to bemut). - The rule: at one time you may have many readers or one writer, never both.
- Borrowing costs nothing and moves nothing — reach for it before you reach for
.clone().
Go deeper
- Rust Book - References and Borrowing — Borrowing rules.
Next: