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

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)

Pro Tips

📋 Quick Reference — Borrowing

GoalRust Syntax
Shared referencelet r = &x;
Mutable referencelet r = &mut x;
Param borrowsfn f(x: &String)
Param borrows mutablyfn f(x: &mut String)
The rulemany & OR one &mut

🎉 Lesson Complete!

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

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.