Rust Borrowing and References Explained: & and &mut, E0499, E0502
November 10, 2026
Welcome to episode 8 of Rust for Beginners, a series for complete beginners (no programming experience needed). In the last video, we learned that passing a String to a function moves it. Today, we'll learn borrowing: how to use a value without taking ownership. Every code block below is the exact code from the video, ready to copy.
- All episodes are in the Rust Tutorial for Beginners playlist.
- Versions used: Rust 1.99.1 on Ubuntu, VS Code with rust-analyzer.
1. New project
Let's create a new project called borrowing, and open it in VS Code.
cargo new borrowing
cd borrowing2. Reference
Here, the length function takes an ampersand String. The ampersand means a reference: it borrows the String, but doesn't own it.
src/main.rs:
fn main() {
let name = String::from("Ferris");
let len = length(&name);
println!("{name} has {len} letters.");
}
fn length(text: &String) -> usize {
text.len()
}Output: Ferris has 6 letters.
3. Borrow
When we call it, we pass ampersand name. name stays the owner, so we can still use it afterwards.
4. Run A
Let's run it. Ferris has six letters.
cargo run5. Read only
Now let's try to change a borrowed String. add world wants to push more text onto it.
cargo runfn main() {
let greeting = String::from("Hello");
add_world(&greeting);
println!("{greeting}");
}
fn add_world(text: &String) {
text.push_str(", world!");
}Error:
error[E0596]: cannot borrow `*text` as mutable, as it is behind a `&` reference
--> src/main.rs:8:5
|
8 | text.push_str(", world!");
| ^^^^ `text` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
7 | fn add_world(text: &mut String) {
| +++6. Error
Error. A normal reference is read-only, so we can't change the value behind it.
7. &mut
To change it, we need a mutable reference: ampersand mut. In three places: the variable must be mut, we pass ampersand mut greeting, and the parameter is ampersand mut String.
fn main() {
let mut greeting = String::from("Hello");
add_world(&mut greeting);
println!("{greeting}");
}
fn add_world(text: &mut String) {
text.push_str(", world!");
}Output: Hello, world!
8. Run B
Let's run it. Hello, world!
cargo run9. Rule 1
Borrowing has two rules. First: only one mutable reference at a time. Here we try to make two.
cargo runfn main() {
let mut s = String::from("hi");
let r1 = &mut s;
let r2 = &mut s;
println!("{r1} and {r2}");
}error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 | println!("{r1} and {r2}");
| -- first borrow later used hereStage C2 โ line 4 becomes let r1 = &s;
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:5:14
|
4 | let r1 = &s;
| -- immutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ mutable borrow occurs here
6 | println!("{r1} and {r2}");
| -- immutable borrow later used here10. Rule 2
Second: you can't have a mutable reference while normal references are still in use.
cargo run11. Why
Why? If one part of the code reads a value while another part changes it, you get bugs. Rust catches them before the program even runs.
12. Works
But many normal references are fine. And once they're no longer used, we can make a mutable one.
cargo runfn main() {
let mut s = String::from("hi");
let r1 = &s;
let r2 = &s;
println!("{r1} and {r2}");
let r3 = &mut s;
r3.push_str(" there");
println!("{r3}");
}Output: hi and hi / hi there
13. Dangling
Last one: a function that returns a reference to its own local String.
cargo runfn main() {
let text = make_text();
println!("{text}");
}
fn make_text() -> &String {
let s = String::from("hello");
&s
}error[E0106]: missing lifetime specifier
--> src/main.rs:6:19
|
6 | fn make_text() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
6 | fn make_text() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
6 - fn make_text() -> &String {
6 + fn make_text() -> String {14. Error
s is dropped when the function ends, so the reference would point to nothing. Rust stops us, and suggests returning an owned String instead.
15. Fix
So let's return the String itself. Ownership moves to the caller, and nothing dangles.
cargo runString:
fn main() {
let text = make_text();
println!("{text}");
}
fn make_text() -> String {
let s = String::from("hello");
s
}Output: hello
Recap
To recap: ampersand borrows a value without owning it. ampersand mut lets you change it. Many readers, or one writer, never both. And references can never dangle.