---
title: References and Borrowing
url: https://doc.liz6.com/en/rust/01-ownership-and-borrowing/02-references-and-borrowing
locale: en
area: rust
tags:
- rust
- ownership-and-borrowing
date: 2026-06-30
modified: 2026-07-16
description: 'The Rust borrow checker follows a single rule: at any given moment, you can have either one mutable reference or multiple immutable references, but never both (aliasing XOR mutation). NLL refines the checking granularity from lexical scope to actual usage intervals, and reborrowing eliminates the need for explicit annotations in nested borrows—these three elements together constitute Rust’s "memory safety without a GC."'
---

# References and Borrowing

> The Rust borrow checker follows a single rule: at any given moment, you can have either one mutable reference or multiple immutable references, but never both (aliasing XOR mutation). NLL refines the checking granularity from lexical scope to actual usage intervals, and reborrowing eliminates the need for explicit annotations in nested borrows—these three elements together constitute Rust’s "memory safety without a GC."

## & vs &mut

```rust
let mut s = String::from("hello");
let r1 = &s;                         // Immutable borrow (shared reference)
let r2 = &s;                         // Multiple immutable borrows are allowed
println!("{} {}", r1, r2);           // OK

let r3 = &mut s;                     // Mutable borrow: requires s to be declared mut
// let r4 = &s;                      // ERROR: Cannot create & while &mut exists
r3.push('!');
```

Core rule — **Aliasing XOR Mutation**: At any given moment, you can have either **multiple shared references** (read-only) or **exactly one mutable reference** (writable), but never both. This rule prevents data races and iterator invalidation.

## How the Borrow Checker Works

The compiler performs borrow checking on the MIR (Mid-level IR). When each borrow is created, it records: which variable was borrowed, whether it is shared or mutable, and until where it lives.

```rust
let mut v = vec![1, 2, 3];
let first = &v[0];                   // Immutable borrow of v starts
v.push(4);                           // ERROR: Mutable borrow of v — first is still live
println!("{}", first);               // Last use of first
// first's borrow ends here
```

Reason for the error: `v.push(4)` requires `&mut v`, but `first` is still in scope holding `&v`. The borrow checker does not allow `&v` and `&mut v` to coexist—even if you know that `push` will not affect `v[0]`, the borrow checker does not know that.

## Non-Lexical Lifetimes (NLL, 2018 Edition)

Before NLL, the lifetime of a borrow was determined by scope (lexical—the entire block from declaration to last use). NLL changes this to **determined by the last use**:

```rust
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{}", first);               // Last use of first
// first's borrow ends here (not at the end of the scope!)

v.push(4);                           // OK: first is no longer live, borrow has ended
// This would not compile before NLL
```

## Reborrow

When passing `&mut T` to a function, the borrow is "reborrowed":

```rust
fn push(v: &mut Vec<i32>) { v.push(5); }

let mut v = vec![1, 2, 3];
push(&mut v);                        // Reborrow: &mut *v → push's &mut v
// After push returns, the reborrow ends → v's ownership is restored

// Equivalent to:
// let reborrow = &mut *v;
// push(reborrow);
// drop(reborrow);  ← reborrow is dropped here, v is restored
```

## Memory Layout of References

`&T` and `&mut T` are just regular pointers at runtime (8 bytes on 64-bit). All borrow checker checks occur at compile time, with zero runtime overhead.

```rust
let x = 42;
let r = &x;                          // Assembly: lea rax, [rsp+4]; mov [rsp+8], rax
```

Fat pointers: `&[T]` and `&dyn Trait` are 16 bytes—storing an extra length or vtable pointer.

## Common Pitfalls

**1. Cannot modify the original value during an immutable borrow**

```rust
let mut v = vec![1, 2, 3];
let r = &v[0];
v.clear();                           // ERROR: &v (immutable) is active, cannot &mut v
```

**2. Iterator invalidation is prevented by the borrow checker**

```rust
let mut v = vec![1, 2, 3];
for item in &v {                     // The for loop implicitly creates a borrow of &v
    v.push(4);                       // ERROR: &v is active, cannot &mut v
}
```

**3. Returning references from functions requires explicit lifetimes**

```rust
fn first_word(s: &str) -> &str {     // Lifetime elision: actually fn<'a>(s: &'a str) -> &'a str
    &s[..s.find(' ').unwrap_or(s.len())]
}
// The returned reference borrows from s → the compiler guarantees that the &str does not outlive s
```

## References

- **Rust Book**: Chapter 4.2 — References and Borrowing
- **NLL RFC**: RFC 2094
- **Polonius**: github.com/rust-lang/polonius (next-gen borrow checker)

*Keywords: borrow checker, shared reference, mutable reference, NLL, reborrow, aliasing XOR mutation, fat pointer*
