References and Borrowing

Multiple Immutable References

Rust allows any number of immutable (shared) references (&T) to the same value to coexist and be read simultaneously. Because no mutation can occur through &T (nor through the owner while borrowed), having multiple readers is 100% free of data races and aliasing hazards.

Concurrent Readers in Action

Multiple Shared References (`&T`) Coexisting Safely:
  reader_1: &Data ──┐
  reader_2: &Data ──┼──> [ Data (Frozen / Read-Only while borrowed) ]
  reader_3: &Data ──┘
#[derive(Debug)]
struct Metrics {
    requests: u64,
    errors: u64,
}

fn error_rate(m: &Metrics) -> f64 {
    (m.errors as f64) / (m.requests as f64)
}

fn main() {
    let stats = Metrics {
        requests: 1000,
        errors: 15,
    };

    // Create three simultaneous shared references to `stats`:
    let r1: &Metrics = &stats;
    let r2: &Metrics = &stats;
    let r3: &Metrics = r1; // Copying `r1` (`&T` implements `Copy`)

    println!("Via r1: requests = {}", r1.requests);
    println!("Via r2: error_rate = {:.2}%", error_rate(r2) * 100.0);
    println!("Via r3: {r3:?}");
}
Via r1: requests = 1000
Via r2: error_rate = 1.50%
Via r3: Metrics { requests: 1000, errors: 15 }

Passing Multiple Shared References to the Same Value into One Function

Because multiple &T borrows are allowed simultaneously, you can pass &x as both arguments to a function fn compare(a: &T, b: &T)—or hold multiple sub-slices (&slice[0..2] and &slice[1..3]) that overlap in memory!

fn dot_product(a: &[i32], b: &[i32]) -> i32 {
    a.iter().zip(b.iter()).map(|(&x, &y)| x * y).sum()
}

fn main() {
    let vector = [1, 2, 3, 4];

    // Pass `&vector` as BOTH arguments to compute its squared magnitude:
    let mag_sq = dot_product(&vector, &vector);

    // Create two OVERLAPPING immutable slices of `vector` at the same time:
    let window_a: &[i32] = &vector[0..3]; // [1, 2, 3]
    let window_b: &[i32] = &vector[1..4]; // [2, 3, 4] (overlaps at indices 1 and 2!)

    println!("mag_sq = {mag_sq}, overlapping windows: {window_a:?} and {window_b:?}");
}
mag_sq = 30, overlapping windows: [1, 2, 3] and [2, 3, 4]

How Lifetime Overlap Governs When Mutation Resumes

When multiple immutable references (r1, r2, r3) borrow a mut variable x, x remains frozen until the last active reader finishes its final read:

fn main() {
    let mut text = String::from("v1");

    let r1 = &text;
    let r2 = &text;
    println!("r1 finishes here: {r1}"); // `r1` lifetime ends here
    println!("r2 finishes here: {r2}"); // `r2` lifetime ends here (last reader!)

    // Now zero readers remain active, so `text` can be mutated:
    text.push_str(" -> v2");
    println!("Mutated: {text}");
}
r1 finishes here: v1
r2 finishes here: v1
Mutated: v1 -> v2