Rust Borrowing

Immutable References

&T — read without taking ownership
fn print_length(s: &String) {
    println!("length: {}", s.len());
}  // s goes out of scope, but it's a reference — nothing is dropped

fn main() {
    let s = String::from("hello");
    print_length(&s);   // borrow s
    println!("{s}");     // s is still valid
}

&s creates a reference. The function borrows the value without taking ownership. Multiple immutable references can coexist.

Mutable References

&mut T — modify without taking ownership
fn add_world(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    add_world(&mut s);
    println!("{s}");  // "hello, world"
}

Mutable references require the variable to be declared mut. Only one &mut T is allowed at a time.

The Borrowing Rules

Two rules enforced at compile time
let mut s = String::from("hello");

// Rule 1: Any number of immutable references
let r1 = &s;
let r2 = &s;
println!("{r1}, {r2}");  // OK

// Rule 2: Exactly one mutable reference, no immutable refs active
let r3 = &mut s;
r3.push_str("!");
// println!("{r1}");  // ERROR: can't use r1 while r3 exists

// After r3's last use, new borrows are fine
println!("{s}");

These rules prevent data races at compile time. You either have many readers or one writer — never both simultaneously.

String Borrowing

String vs &str — owned vs borrowed
// &str is a borrowed string slice — a view into string data
fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' {
            return &s[..i];
        }
    }
    s
}

let owned = String::from("hello world");
let word = first_word(&owned);    // &String coerces to &str
println!("{word}");                // "hello"

let literal = "hello world";       // &str (string literal)
let word = first_word(literal);    // already &str

Accept &str in function signatures — it accepts both &String (via deref coercion) and string literals. Return String when the function creates new data; return &str when it returns a view.

Slice Borrowing

Vec<T> vs &[T] — owned vs borrowed
fn sum(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

let v = vec![1, 2, 3, 4, 5];
let total = sum(&v);       // &Vec<i32> coerces to &[i32]
let partial = sum(&v[1..4]); // borrow a sub-slice

&[T] is a slice reference — a view into contiguous memory. Like &str for strings, accept &[T] to be flexible about what callers can pass.

Reborrowing

The compiler helps you when possible
let mut v = vec![1, 2, 3];
let r = &mut v;
// The compiler "reborrows" r for the function call
r.push(4);    // uses &mut *r implicitly
r.push(5);    // r is still usable
println!("{:?}", r);

Mutable references are automatically reborrowed when passed to functions. This means you can call multiple methods on &mut T without explicitly creating new references each time.