Rust Borrowing

Borrowing lets code access a value through a reference without taking ownership of it, and the borrow checker enforces strict rules about how references can be used.

What Is a Reference?

A reference lets you refer to a value without taking ownership of it. References are created with the `&` operator and are said to "borrow" the value they point to. Because borrowing does not transfer ownership, the original owner remains responsible for dropping the value, and the borrowed data must not be used after the owner goes out of scope.

Borrowing with an Immutable Reference

fn calculate_length(text: &String) -> usize {
    text.len() // reading through the reference, not owning it
}

fn main() {
    let message = String::from("borrowed");
    let length = calculate_length(&message);

    println!("'{}' has length {}", message, length); // still valid
}

Mutable References

A mutable reference, written `&mut T`, allows the borrowed code to modify the value it points to. To create one, both the original variable and the reference must be declared `mut`. Mutable references let you pass a value into a function for modification without transferring ownership or requiring the function to return it back.

Borrowing with a Mutable Reference

fn append_greeting(text: &mut String) {
    text.push_str(", hello!");
}

fn main() {
    let mut message = String::from("hi there");
    append_greeting(&mut message);

    println!("{}", message);
}

The Borrowing Rules

The borrow checker enforces two rules for any given value, at any given point in the program: you may have any number of immutable references (`&T`) at once, OR exactly one mutable reference (`&mut T`), but never both kinds at the same time. Additionally, references must always be valid — Rust will not allow a reference to outlive the data it points to, which rules out dangling references entirely.

  • Any number of immutable references (`&T`) may coexist simultaneously.
  • Only one mutable reference (`&mut T`) may exist at a time, with no other references active.
  • Immutable and mutable references to the same value cannot coexist.
  • A reference's scope ends at its last use, not necessarily at the closing brace (non-lexical lifetimes).
  • The borrow checker rejects any reference that could outlive the data it refers to.

Multiple Immutable Borrows Are Fine

fn main() {
    let numbers = vec![1, 2, 3, 4];

    let first_half = &numbers[..2];
    let second_half = &numbers[2..];

    println!("{:?} and {:?}", first_half, second_half);
    // multiple immutable borrows of `numbers` coexist safely
}
Note: The single most common borrow-checker error is "cannot borrow as mutable because it is also borrowed as immutable" (or the reverse). It happens when a mutable and an immutable reference to the same data overlap in their live ranges — often because an immutable reference is still being used later in the function than it appears at first glance. The fix is usually to shrink the immutable reference's scope, or restructure the code so the two borrows do not overlap in time.

Dangling References

Rust's compiler statically prevents dangling references — references to memory that has already been freed. If a function tries to return a reference to a value it created locally, the compiler rejects the code at compile time, because that local value would be dropped when the function returns, leaving the reference pointing at freed memory.

The Compiler Rejects Dangling References

// fn dangle() -> &String {
//     let s = String::from("oops");
//     &s // error: `s` is dropped at the end of this function
// }

fn not_dangling() -> String {
    let s = String::from("safe");
    s // ownership moves out instead of a reference
}

fn main() {
    let value = not_dangling();
    println!("{}", value);
}
Reference kindSyntaxCoexistence rule
Immutable reference&TMany allowed simultaneously
Mutable reference&mut TOnly one allowed, and no others of any kind
No reference (owned)TFull ownership, no sharing
Note: These borrowing rules are checked entirely at compile time and have zero runtime cost, which is why Rust can guarantee memory and data-race safety without a garbage collector.

Exercise: Rust Borrowing

What does creating a reference with `&value` do?