M Logo
๐Ÿ“ Rust for Beginners ยท Part 8 of 22

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.

1. New project

Let's create a new project called borrowing, and open it in VS Code.

bash
cargo new borrowing
cd borrowing

2. 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:

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

bash
cargo run

5. Read only

Now let's try to change a borrowed String. add world wants to push more text onto it.

bash
cargo run
rust
fn main() {
    let greeting = String::from("Hello");
    add_world(&greeting);
    println!("{greeting}");
}

fn add_world(text: &String) {
    text.push_str(", world!");
}

Error:

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

rust
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!

bash
cargo run

9. Rule 1

Borrowing has two rules. First: only one mutable reference at a time. Here we try to make two.

bash
cargo run
rust
fn main() {
    let mut s = String::from("hi");

    let r1 = &mut s;
    let r2 = &mut s;
    println!("{r1} and {r2}");
}
text
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 here

Stage C2 โ€“ line 4 becomes let r1 = &s;

text
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 here

10. Rule 2

Second: you can't have a mutable reference while normal references are still in use.

bash
cargo run

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

bash
cargo run
rust
fn 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.

bash
cargo run
rust
fn main() {
    let text = make_text();
    println!("{text}");
}

fn make_text() -> &String {
    let s = String::from("hello");
    &s
}
text
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.

bash
cargo run

String:

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

ยฉ 2026 All Rights Reserved By Mahmudul Alam