BudiBadu Logo
Samplebadu

Rust by Example: Borrowing and References

Rust 1.75+

Learn how to use data without taking ownership of it. This guide explains immutable and mutable references, the rules of borrowing, and how Rust ensures memory safety through compile-time checks on reference validity.

Code

fn main() {
    let s1 = String::from("hello");
    
    // Pass a reference (&s1) instead of moving ownership
    let len = calculate_length(&s1);
    
    // s1 is still valid here because we only "borrowed" it
    println!("The length of '{}' is {}.", s1, len);
    
    // --- Mutable References ---
    let mut s2 = String::from("hello");
    
    change(&mut s2);
    println!("Changed string: {}", s2);
}

fn calculate_length(s: &String) -> usize {
    // s is a reference to a String
    s.len()
    // s goes out of scope, but because it doesn't have ownership,
    // nothing happens (the string is not dropped)
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Explanation

Moving ownership every time we want to use a variable would be tedious. Rust solves this with References, also known as Borrowing. A reference allows you to refer to some value without taking ownership of it. Under the hood, a reference is just a pointer (usually 8 bytes on 64-bit systems) that points to the memory location of the original data.

References are created using the ampersand & symbol. &s1 creates a reference to the value of s1. Because the function calculate_length only borrows s1, the original variable remains valid after the function call returns. By default, references are immutable; you cannot modify the borrowed value.

If you need to modify the borrowed value, you use a Mutable Reference (&mut). However, Rust enforces a strict rule to prevent data races, known as "Aliasing XOR Mutability":

  • You can have either one mutable reference...
  • ...OR any number of immutable references.
  • But NOT both at the same time.
This rule is enforced by the Borrow Checker at compile time, eliminating an entire category of concurrency bugs without runtime overhead.

Code Breakdown

5
calculate_length(&s1). The & creates a reference. We are passing a pointer to the data, not the data itself. This means s1 retains ownership.
13
change(&mut s2). We create a mutable reference using &mut. The variable s2 must also be declared as mut for this to work.
17
fn calculate_length(s: &String). The function signature explicitly states that it expects a reference to a String (&String), not the String itself.