Lesson 7

References & Borrowing

Learn how to use references to borrow values without taking ownership.

Borrowing Instead of Moving

Moving ownership every time you call a function gets tedious. That's where references come in. A reference lets you use a value without taking ownership -- this is called borrowing.

fn calculate_length(s: &String) -> usize {
    s.len()
}  // s goes out of scope, but it doesn't own the String, so nothing is dropped

Mutable References

By default, references are immutable -- you can look but not touch. To modify borrowed data, use a mutable reference with &mut. There's one big rule: you can have only one mutable reference to a value at a time. This prevents data races at compile time.

The Rules

At any given time, you can have either one mutable reference or any number of immutable references. References must always be valid -- Rust won't let you create dangling references. These rules are enforced at compile time with zero runtime cost.

Try it

fn main() {
    // --- Immutable reference (borrowing) ---
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);
    // s1 is still valid! We only borrowed it.

    // --- Multiple immutable references are fine ---
    let s2 = String::from("world");
    let r1 = &s2;
    let r2 = &s2;
    println!("r1: {}, r2: {}", r1, r2);

    // --- Mutable reference ---
    let mut s3 = String::from("hello");
    println!("Before: {}", s3);
    add_world(&mut s3);
    println!("After: {}", s3);

    // --- Practical example: modifying a value through a reference ---
    let mut scores = vec![85, 92, 78, 95, 88];
    println!("\nOriginal scores: {:?}", scores);
    add_bonus(&mut scores, 5);
    println!("After +5 bonus: {:?}", scores);

    let avg = average(&scores);
    println!("Average score: {:.1}", avg);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn add_world(s: &mut String) {
    s.push_str(", world");
}

fn add_bonus(scores: &mut Vec<i32>, bonus: i32) {
    for score in scores.iter_mut() {
        *score += bonus;
    }
}

fn average(scores: &Vec<i32>) -> f64 {
    let sum: i32 = scores.iter().sum();
    sum as f64 / scores.len() as f64
}

Challenge

Change the add_world function to accept a custom word instead of always appending ", world". Make it take a second &str parameter.

Previous: Ownership

Next: Structs