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.