Rust

Borrowing (Rust)

Borrowing lets a function use a value without taking ownership, by passing a reference (&T or &mut T) instead of moving the value.

Why borrowing?

Pure Ownership (Rust) would force every function to return its arguments back to the caller. Borrowing promises ā€œI’ll use it and give it backā€, and the borrow checker enforces the promise at compile time.

Immutable borrow

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { s.len() }

s1 is still valid after the call because only a reference was passed.

Borrow-checker rules:

  • References are immutable by default, &mut is opt-in
  • While a &mut T exists, no other references (mutable or immutable) may coexist, and the owner can’t mutate through itself
  • Any number of &T may coexist (reads don’t race)
  • A reference cannot outlive the value it points to

Dangling references

fn dangle() -> &String {
    let s = String::from("hello");
    &s // s is dropped here, reference would point at freed memory
}

The borrow checker rejects this at compile time. In C, the analogous mistake compiles and produces a dangling pointer that may appear to work at first.

Non-Lexical Lifetimes

A borrow ends at its last use, not at the closing brace. This lets let z = &mut x; compile after the last read of &x, even if &x is still nominally in scope.

The exclusivity of &mut is what eliminates data races statically: no reader can observe a write in progress if writes are exclusive.