References & Borrowing
Reviewed & published by Brayan K
Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and borrowing lets you use a value without taking ownership of it, which makes ownership practical day to day.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll create shared references with &, mutable references with &mut, and learn the borrow checker's rules that keep this all safe.
What You'll Learn in This Lesson
- How to create a reference with &
- How borrowing avoids moving ownership
- How to make a mutable reference with &mut
- The borrowing rules the compiler enforces
- Why the owner must be mut to lend out &mut
- Why borrowing is preferred over cloning
1️⃣ Shared References with &
A reference lets a function access a value without owning it. You create one with the & operator and accept one with a & type. Because the function only borrows, the original variable stays valid after the call — no move, no clone needed.
fn main() {
let s = String::from("hello");
// & creates a REFERENCE — we lend s to the function without moving it.
// The function borrows s; it does not take ownership.
let len = calculate_length(&s);
// Because we only borrowed, s is STILL valid here.
println!("'{}' has length {}", s, len); // -> 'hello' has length 5
}
// &String means "a reference to a String". The function can read it but
// does not own it, so s is not dropped when the function ends.
fn calculate_length(text: &String) -> usize {
text.len()
}Compare this with the previous lesson: there, passing a String into a function moved it away. Here &s lends it, so s is still usable on the next line.
2️⃣ Mutable References and the Borrow Rules
To let a borrower change a value, use a mutable reference &mut. The borrow checker enforces a strict rule: at any moment you can have either any number of shared & references or exactly one &mut reference — never both. This is what prevents data races at compile time.
fn main() {
let mut message = String::from("hello");
// &mut creates a MUTABLE reference, allowing the function to change it.
// The owner (message) must itself be mut to lend out a &mut.
add_excitement(&mut message);
println!("{}", message); // -> hello!!!
// BORROW RULES: you may have EITHER many shared (&) references OR
// exactly one mutable (&mut) reference at a time — never both.
let r1 = &message; // shared borrow
let r2 = &message; // another shared borrow — fine
println!("{} and {}", r1, r2); // r1, r2 last used here
// Now that r1/r2 are done, a mutable borrow is allowed.
let r3 = &mut message;
r3.push_str("?");
println!("{}", r3); // -> hello!!!?
}
fn add_excitement(text: &mut String) {
text.push_str("!!!"); // modify the borrowed String in place
}Notice the ordering: the two shared borrows r1 and r2 are used and finished before the mutable borrow r3 begins. Overlapping a shared and a mutable borrow would be a compile error.
Your turn. Fill in the blanks marked ___, then run it.
// 🎯 YOUR TURN — finish the borrows, then run it.
// 1) This function should READ the string's length without taking ownership.
// Fill in the reference type.
fn length(text: ___) -> usize { // 👉 a shared reference to a String
text.len()
}
// 2) This function should MODIFY the string by adding "!".
// Fill in the mutable reference type.
fn excite(text: ___) { // 👉 a mutable reference to a String
text.push_str("!");
}
fn main() {
let mut word = String::from("hi");
// Pass a shared reference (read only).
println!("length = {}", length(&word)); // expected: length = 2
// Pass a mutable reference (modifies word).
excite(&mut word);
println!("word = {}", word); // expected: word = hi!
}
// ✅ Expected output:
// length = 2
// word = hi!Mini-Challenge: A Borrowed Greeter
Write two functions — one that borrows immutably, one that borrows mutably — and use both from main. Run it with cargo run.
// 🎯 MINI-CHALLENGE: a borrowed greeter.
//
// 1. Write fn greet(name: &String) that prints "Hello, <name>!" — it must
// BORROW name (use &String), not take ownership.
// 2. Write fn shout(text: &mut String) that appends " WOW" to text.
// 3. In main: make a mutable String "Sam". Call greet with a shared
// reference, then shout with a mutable reference, then print the String.
//
// ✅ Example output:
// Hello, Sam!
// Sam WOW
fn main() {
// your code here
}Common Errors (and the fix)
- "cannot borrow as mutable, as it is not declared as mutable" — The owner needs mut: declare it let mut s = ... before taking a &mut s.
- "cannot borrow as mutable more than once at a time" — You have two &mut to the same value. Only one mutable borrow may be active at once.
- "cannot borrow as immutable because it is also borrowed as mutable" — A shared and a mutable borrow overlap. Finish using one before starting the other.
- Forgetting the & at the call site — If the function takes &String, call it with f(&s), not f(s) (which would move).
Pro Tips
- 💡 Borrow to read, &mut to write: use a shared reference whenever you only need to look at a value.
- 💡 Prefer &str over &String: taking &str makes functions more flexible — you'll see why next lesson.
- 💡 Let the borrow checker teach you: its messages usually name the conflicting borrows and where each is used.
📋 Quick Reference — Borrowing
| Goal | Rust Syntax |
|---|---|
| Shared reference | let r = &x; |
| Mutable reference | let r = &mut x; |
| Param borrows | fn f(x: &String) |
| Param borrows mutably | fn f(x: &mut String) |
| The rule | many & OR one &mut |
🎉 Lesson Complete!
- ✅ A reference &x lends access without moving ownership
- ✅ &mut x lends mutable access; the owner must be mut
- ✅ Borrow rule: many shared & OR one &mut, never both
- ✅ References must always point to valid data — no dangling pointers
Practice quiz
What does the & operator create when you write f(&s)?
- A copy of s
- A move of s into f
- A reference that borrows s without taking ownership
- A mutable clone of s
Answer: A reference that borrows s without taking ownership. & creates a reference: the function borrows the value, so the original stays valid after the call.
After let len = calculate_length(&s); is s still usable on the next line?
- Yes, because it was only borrowed
- No, it was moved
- Only if s is mut
- Only if you clone it first
Answer: Yes, because it was only borrowed. Borrowing with & does not move ownership, so s remains valid after the call.
What is the borrow rule the compiler enforces?
- Any number of &mut at once
- Exactly one & and one &mut together
- References are never allowed
- Either many shared & OR exactly one &mut, never both
Answer: Either many shared & OR exactly one &mut, never both. At any time you may have many shared references or one mutable reference, but not both simultaneously.
Why must the owner be declared with mut to create a &mut reference to it?
- For naming reasons only
- Because a mutable reference can change the value, so the owner must permit mutation
- Because &mut copies the value
- It does not need to be mut
Answer: Because a mutable reference can change the value, so the owner must permit mutation. You can only hand out a &mut to a value whose owner allows mutation, declared with let mut.
What does this print? let s = String::from("hello"); println!("{}", calculate_length(&s)); // fn calculate_length(text: &String) -> usize { text.len() }
- 5
- hello
- &hello
- It does not compile
Answer: 5. text.len() returns the byte length of "hello", which is 5.
What does add_excitement print? let mut m = String::from("hello"); add_excitement(&mut m); println!("{}", m); // fn add_excitement(text: &mut String) { text.push_str("!!!"); }
- hello
- !!!
- hello!!!
- It does not compile
Answer: hello!!!. The &mut reference lets the function append "!!!" in place, so m becomes hello!!!.
Which is valid at the same time?
- Two &mut to the same value
- Several shared & references at once
- One &mut and one & overlapping
- A reference to dropped data
Answer: Several shared & references at once. Multiple shared references are fine together; only a single &mut is exclusive.
How do you call a function declared as fn f(x: &String)?
- f(s)
- f(*s)
- f(mut s)
- f(&s)
Answer: f(&s). Pass a reference with &s; calling f(s) would move the String instead of borrowing it.
Which is more general and usually preferred in function signatures?
- &String
- &str
- String
- Box<String>
Answer: &str. &str is more general; a String coerces to &str, so idiomatic functions take &str.
What guarantee do Rust references always provide?
- They are nullable
- They may dangle
- They always point to valid data (no dangling pointers)
- They are always mutable
Answer: They always point to valid data (no dangling pointers). The borrow checker ensures references never outlive the data they point to.
Continue this course
- Previous: Ownership
- Next: Slices — Reference contiguous sections of collections without copying them
- Quick reference: Rust cheat sheet › Ownership & Borrowing
Frequently asked questions
What is the difference between a move and a borrow?
A move transfers ownership: after 'let b = a' for a String, a is gone. A borrow lends temporary access without transferring ownership: 'f(&a)' lets f read a, and after the call a is still yours. You borrow with the & operator. Borrowing is the everyday way to let functions and other code use a value without the ceremony of moving it in and handing it back, and it is far more common than moving in real Rust code.
What are the borrowing rules?
At any given time you may have either any number of shared (immutable) references &T, OR exactly one mutable reference &mut T — but never both at the same time. This is enforced at compile time by the borrow checker. The rule prevents data races and aliasing bugs: if something can be read from many places, nothing may be writing to it simultaneously. References must also always point to valid data; you can never have a dangling reference.
Why must the owner be declared mut to create a &mut reference?
A mutable reference lets you change the underlying value, so the value's owner has to permit mutation in the first place. If you declare 'let s = String::from(...)' without mut, the value is immutable and you cannot hand out a &mut to it. Declaring 'let mut s = ...' tells Rust the value is allowed to change, which is what makes lending a &mut s legal.
What is the difference between &String and &str?
&String is a reference to an owned String, while &str (a string slice) is a reference to a run of UTF-8 text that could live in a String, a string literal, or elsewhere. Because &str is more general, idiomatic Rust functions usually take &str rather than &String — a String automatically coerces to &str when you pass it. You'll explore slices, including &str, in the very next lesson.