DaleSchool

Borrowing and References

Intermediate20min

Learning Objectives

  • Use & to borrow a value
  • Explain the difference between immutable and mutable references
  • Understand that only one mutable reference can exist at a time
  • Read and fix borrow-checker errors

Working Code

In the previous lesson you learned that passing a value to a function moves ownership. clone() works, but copying every time is wasteful.

There's a better way — borrowing!

fn print_message(msg: &String) {
    // msg is borrowed — read-only access
    println!("{msg}");
}

fn main() {
    let message = String::from("Hello!");
    print_message(&message);     // lend it out
    println!("Still usable: {message}"); // owner unchanged!
}

The & is the key. &message means "I'm lending this value," and msg: &String means "I'm receiving a borrowed value."

Try It Yourself

  1. Fix the error-prone code from Lesson 09 using borrowing:
fn print_fruits(fruits: Vec<&str>) {
    for f in fruits {
        println!("{f}");
    }
}

fn main() {
    let fruits = vec!["apple", "banana"];
    print_fruits(fruits);
    println!("Number of fruits: {}", fruits.len()); // error!
}

Hint: Add & to the function parameter and the call site.

  1. Can you get the same result without clone()? Try it!

"Why?" — Think of It Like a Library Loan

Borrowing is easier to understand as a library loan:

  • You borrow a book (value) from the library (owner), but the library remains the owner
  • You can read the book, but you can't tear out pages (immutable borrow)
  • When the loan period is over, you return it
fn calculate_length(s: &String) -> usize {
    s.len()
    // the borrowed reference is returned when the function ends
}

fn main() {
    let word = String::from("Rust");
    let len = calculate_length(&word);
    println!("Length of {word}: {len}");
}

Because you lent with &word, ownership didn't move. The borrowed reference is automatically returned when the function ends.

Mutable Borrowing — Edit Permissions

What if you need to modify a value, not just read it? Use &mut.

fn add_exclamation(s: &mut String) {
    s.push_str("!!!");
}

fn main() {
    let mut message = String::from("hello");
    add_exclamation(&mut message);
    println!("{message}"); // "hello!!!"
}

Think of mutable borrowing as edit permissions:

  • Read permission (&): Multiple people can have it at the same time
  • Edit permission (&mut): Only one person can have it at a time

Why is editing limited to one? If multiple people edit the same document simultaneously, the content gets corrupted. Rust prevents this at compile time.

Borrowing Rules Summary

  1. You can have multiple immutable references (&) at the same time
  2. You can have only one mutable reference (&mut) at a time
  3. Immutable and mutable references cannot coexist
fn main() {
    let mut data = String::from("hello");

    let r1 = &data;     // OK — read permission 1
    let r2 = &data;     // OK — read permission 2
    println!("{r1}, {r2}");

    let r3 = &mut data; // OK — the read permissions are no longer in use
    r3.push_str(" world");
    println!("{r3}");
}
error[E0502]: cannot borrow `data` as mutable because it is
              also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &data;
  |              ----- immutable borrow occurs here
5 |     let r2 = &data;
6 |     let r3 = &mut data;
  |              ^^^^^^^^^ mutable borrow occurs here
7 |     println!("{r1}, {r2}");
  |               --- immutable borrow later used here

What it means: "You borrowed data immutably and then tried to borrow it mutably at the same time. You can't grant edit access while read access is still in use."

Fix: Create the mutable reference after you're done using the immutable ones.

Learn More: NLL — How Rust Got Smarter

Since Rust 2018, NLL (Non-Lexical Lifetimes) treats a reference as valid only until its last point of use.

fn main() {
    let mut data = String::from("hello");

    let r1 = &data;
    println!("{r1}");      // last use of r1

    let r2 = &mut data;    // OK! r1 is no longer in use
    r2.push_str(" world");
    println!("{r2}");
}

This used to be an error, but now the compiler recognizes that "r1 isn't used anymore" and allows the mutable borrow.

Exercise 1 (easy). Fix the code below using borrowing.

fn greet(name: String) {
    println!("Hello, {name}!");
}

fn main() {
    let name = String::from("Alice");
    greet(name);
    greet(name); // error! name was moved
}

Hint: Use & so the function doesn't take ownership.

Exercise 2 (medium). Fix the error in this code.

fn main() {
    let mut numbers = vec![1, 2, 3];
    let first = &numbers[0];
    numbers.push(4);
    println!("First: {first}");
}

Hint: numbers.push(4) requires a mutable borrow of the vector. What if you use first before calling push?

Exercise 3 (challenge). Complete the function below. It should return the first word of a string.

fn first_word(s: &String) -> &str {
    // Complete this!
    // Hint: use s.find(' ') and &s[..index]
}

fn main() {
    let sentence = String::from("hello world");
    let word = first_word(&sentence);
    println!("First word: {word}");
}

Q1. What does the & symbol mean?

  • A) It copies the value
  • B) It creates a reference (borrow) to the value
  • C) It deletes the value
  • D) It changes the type of the value

Q2. Why does this code produce an error?

fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &mut s;
    println!("{r1}");
}
  • A) String cannot be borrowed
  • B) An immutable and a mutable reference exist at the same time
  • C) mut was used twice
  • D) You can't pass a reference to println!

Q3. How many mutable references (&mut) can exist at the same time?

  • A) Unlimited
  • B) 2
  • C) 1
  • D) 0 (they can't be created)