References and Borrowing

Borrowing and Collections

Collections such as Vec<T>, String, HashMap<K, V>, and VecDeque<T> store their elements in a single dynamically resizable heap buffer. Because resizing, inserting, or removing elements can reallocate or shift the entire heap buffer, borrowing even a single element (&vec[0] or map.get("key")) borrows the entire collection for the lifetime of that element reference.

Why Borrowing One Element Locks the Whole Collection

Unlike struct fields (s.a and s.b), which have fixed compile-time offsets on the stack, a collection's indexing operator vec[i] is a method call (Index::index(&self, i) -> &T). Because its signature takes &self, holding &vec[0] keeps the entire vec immutably borrowed:

let first: &i32 = &vec[0];    (Calls `Index::index(&vec, 0)` -> borrows ALL of `vec` as `&Vec<i32>`)
vec.push(99);                 (Requires `&mut vec` -> ❌ E0502 while `first` is still in use!)
fn main() {
    let mut scores = vec![80, 90, 100];

    // 1. Copy the `i32` value out (or finish using the reference) BEFORE mutating the Vec:
    let first_val: i32 = scores[0]; // Copies the `i32`; borrow of `scores` ends on this line!
    scores.push(110);               // Allowed!

    println!("first_val = {first_val}, scores = {scores:?}");
}
first_val = 80, scores = [80, 90, 100, 110]

Storing Indices Instead of Element References

When you need to remember a position inside a Vec<T> across multiple .push() or .remove() operations, store the usize index (such as let first_idx = 0;) rather than an element reference &T. Because a usize is an independent integer, it does not borrow the Vec<T> at all!

Updating a HashMap Entry in Place (entry API)

In a HashMap<K, V>, checking if let Some(v) = map.get(&k) and then calling map.insert(k, new_v) inside the block can cause borrow conflicts or double hash lookups. Instead, use .entry(key).or_insert(...) (or .and_modify(...)), which returns a single &mut V reference to the value slot:

use std::collections::HashMap;

fn main() {
    let words = ["rust", "borrow", "rust", "vec", "rust", "borrow"];
    let mut counts: HashMap<&str, u32> = HashMap::new();

    for word in words {
        // `.entry(word).or_insert(0)` returns `&mut u32` in a single lookup:
        let count_slot: &mut u32 = counts.entry(word).or_insert(0);
        *count_slot += 1;
    }

    println!("Word counts: {counts:?}");
}
Word counts: {"rust": 3, "borrow": 2, "vec": 1}

Mutating Elements While Iterating (iter_mut and retain)

You can never call vec.push(x) or vec.remove(i) inside a for item in &vec loop because the loop header holds an active borrow of vec.

  • To modify existing elements in place during a loop: Iterate over &mut vec (for item in &mut vec { *item += 1; }).
  • To remove elements matching a condition in place: Call vec.retain(|item| keep_condition):
fn main() {
    let mut numbers = vec![1, -4, 7, -2, 10];

    // Remove all negative numbers in place in O(n) time without borrow conflicts:
    numbers.retain(|&n| n >= 0);

    println!("Positive numbers retained: {numbers:?}");
}
Positive numbers retained: [1, 7, 10]