Rust Essential Course – Lesson 6

Lesson 6: Borrowing and References in Rust

What Are References and Borrowing?

In Rust, ownership ensures only one variable owns any given data at a time, but sometimes, you want to allow functions to read or even modify data without taking ownership. That’s where references and borrowing come into play.

  • reference lets you access data without owning it—essential for sharing or lending values to other parts of your program.
  • The act of creating a reference is called borrowing. You “borrow” data for reading (immutable) or modifying (mutable).

Immutable References

You can create any number of immutable references (read-only borrows) at a time.

fn main() {
    let str_data = String::from("Hello, world!");
    print_length(&str_data);
    println!("Still own str_data: {}", str_data);
}

fn print_length(s: &String) {
    println!("Length is: {}", s.len());
}
  • The &str_data creates an immutable reference.
  • The function receives &String and can read but not modify.
  • Ownership stays with the original variable, so you can use it after the function call.

Mutable References

Mutable references let you modify the borrowed value—but with important restrictions!

  • Only one mutable reference to a value can exist at a time—this prevents data races and bugs!
  • Pass mutable references with &mut.
fn main() {
    let mut greeting = String::from("Hello");
    change_message(&mut greeting);
    println!("Now greeting says: {}", greeting);
}

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

Rust’s Reference Rules

Borrowing must follow these rules:

  • Any number of immutable references OR a single mutable reference to a value, but never both at the same time.
  • References must always point to valid data—Rust ensures safety at compile time.
  • References expire at the end of their scope.

Example: Immutable and Mutable References

fn main() {
    let mut data = String::from("hello");
    let a = &data;
    let b = &data;
    println!("a: {}, b: {}", a, b); // Both are valid
    // Now, after a and b are done, create a mutable reference:
    let c = &mut data;
    c.push_str(", world");
    println!("c: {}", c);
}
  • Multiple immutable reads allowed in the same scope, or a single mutable reference after.

Interactive Task 1: Practice Borrowing

Change the following program to use a function that can either read or modify the string. Observe what happens when you try to use both a mutable and an immutable reference at once.

fn main() {
    let mut word = String::from("borrow me");
    print_str(&word);        // pass immutable reference
    change_str(&mut word);   // pass mutable reference
    
    // Uncomment the next line and see what happens:
    // print_str(&word);
}

fn print_str(s: &String) {
    println!("{}", s);
}

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

Try here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=5ad6af4fa3f67f9a834f8f1796503e93

Mini-Challenge

Write a function make_shouty that takes a mutable reference to a String and turns its content uppercase (use .make_ascii_uppercase()).

  • Call your function from main and print the result.
fn main() {
    let mut cheer = String::from("go rust");
    // Your code here!
}

Solution: https://gist.github.com/rust-play/0acc394177bc3012d2e47af4d45d7d58

Recap

  • References (&T) let you borrow data for reading or writing.
  • Immutable references: Multiple at the same time.
  • Mutable references: Only one at a time, enforced by the compiler.
  • These rules make Rust programs safe from common concurrency and memory errors.

Questions or code to share? Drop a comment!

Leave a Comment

Your email address will not be published. Required fields are marked *