# Rust Syntax Borrowing

> Use when the user writes code that triggers borrow-checker errors, asks "can I have & and &mut at the same time", encounters E0502 / E0499 / E0596, needs interior mutability (Cell / RefCell), splits a borrow across struct fields, or asks about reborrowing. Prevents borrow-checker fights via clone(), introduces interior mutability prematurely, or assumes NLL handles every case. Covers: `&T` (shared) vs `&mut T` (exclusive) rules, Non-Lexical Lifetimes (NLL), two-phase borrows, reborrowing, splitting borrows across struct fields and slices (`split_at_mut`), interior mutability (Cell for Copy, RefCell runtime-checked, Mutex thread-safe). Keywords: borrow, borrowing, "&", "&mut", "shared reference", "mutable reference", NLL, "non-lexical lifetimes", reborrow, "two-phase borrow", "split borrow", "split_at_mut", "interior mutability", Cell, RefCell, Mutex, E0502, E0499, E0596, "cannot borrow as mutable", "already borrowed", "multiple mutable borrows".

- Skill: `impertio-studio/rust-syntax-borrowing` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/rust-syntax-borrowing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/rust-syntax-borrowing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/rust-syntax-borrowing

---


# rust-syntax-borrowing

Mechanics of `&T` / `&mut T`, Non-Lexical Lifetimes, two-phase borrows, reborrowing, split borrows, and interior mutability. The borrow checker is **not your enemy**: most errors point to a real soundness issue that `.clone()` would only paper over.

Cross-references: [[rust-syntax-ownership]] (move semantics is the alternative to borrowing), [[rust-syntax-lifetimes]] (lifetime annotations on borrows), [[rust-errors-borrow-checker]] (E-code-specific decoding), [[rust-core-memory-model]] (conceptual overview).

---

## When to use this skill

- User writes code that fails with `E0502` ("cannot borrow `x` as mutable because it is also borrowed as immutable"), `E0499` ("cannot borrow `x` as mutable more than once at a time"), or `E0596` ("cannot borrow `x` as mutable, as it is not declared as mutable").
- User asks "can I have a `&` and `&mut` at the same time".
- User reaches for `.clone()` to silence the borrow checker.
- User asks "what is `RefCell` / `Cell` / interior mutability".
- User wants to mutate two fields of a struct from two threads / two closures / two scopes.
- User asks "how do I get two mutable references into different parts of a `Vec`".
- User asks "what is reborrowing" or "why does my `&mut` get passed but the original keeps working".

---

## The borrow rules (verbatim from the Rust Book)

> At any given time, you can have **either one mutable reference or any number of immutable references**. References must always be valid.

ALWAYS state this rule first when explaining borrow-checker errors. It is the **only** rule you ever need; everything else (NLL, two-phase, split borrows) is the compiler being smart about *when* this rule is checked.

Encoded as a truth table:

| Active reference set on `x` | Allowed? | Why |
|-----------------------------|----------|-----|
| zero refs | yes | No aliasing concern. |
| N `&x` (N >= 1), no `&mut x` | yes | Shared reads are race-free. |
| exactly one `&mut x`, no `&x` | yes | Unique writer, no aliasing. |
| any `&x` + any `&mut x` simultaneously | NO | E0502 / E0499. |
| two `&mut x` simultaneously | NO | E0499. |

---

## Quick reference table

| Concept | One-line rule |
|---------|---------------|
| `&T` | Shared (immutable) reference. Many allowed. `Copy`. |
| `&mut T` | Exclusive (mutable) reference. Exactly one allowed. Not `Copy`. |
| NLL | A borrow ends at its **last use**, not at end of scope. |
| Two-phase borrow | A `&mut` is split into a reservation phase (acts like `&`) and an activation phase. Allows `vec.push(vec.len())`. |
| Reborrow | `&mut *r` (often implicit). Creates a new borrow that ends when used; the original `&mut` remains live afterwards. |
| Split borrow (struct) | Borrowing two distinct fields of `&mut self` is allowed. The compiler tracks disjoint paths. |
| Split borrow (slice) | `slice.split_at_mut(i)` returns `(&mut [T], &mut [T])` over disjoint halves. |
| `Cell<T>` | Interior mutability for `Copy` types or move in/out. No runtime cost. `!Sync`. |
| `RefCell<T>` | Interior mutability with runtime borrow checking. Panics on violation. `!Sync`. |
| `Mutex<T>` | Thread-safe interior mutability. Blocks on contention. `Send + Sync` if `T: Send`. |

---

## NLL: borrows end at last use

Since Rust 2018 (and improved in every release since), borrows end at their **last use**, not at the closing brace. This is called Non-Lexical Lifetimes.

```rust
fn nll_demo() {
    let mut v = vec![1, 2, 3];
    let r = &v[0];          // immutable borrow starts
    println!("{r}");        // immutable borrow ends here (last use of `r`)
    v.push(4);              // OK: no live borrow at this point
}
```

Pre-NLL (Rust 2015 lexical) the immutable borrow would have lasted to the `}` and `v.push(4)` would fail with E0502.

ALWAYS look at the **last use** of a borrow before claiming the borrow checker is wrong. If `r` is never read after a certain point, the borrow is dead from that point on.

NEVER assume the borrow is alive for the whole `{}` block. That is the pre-NLL mental model and is wrong on edition 2018+.

---

## Two-phase borrows: why `vec.push(vec.len())` compiles

A `&mut` borrow is split into two phases by the compiler:

1. **Reservation phase**: the `&mut` is created but acts like an `&` (allows other shared reads).
2. **Activation phase**: the `&mut` becomes exclusive at the point the function call actually mutates.

```rust
fn two_phase_demo() {
    let mut v = vec![1, 2, 3];
    v.push(v.len());        // OK: reservation of &mut v starts at call,
                            //     v.len() reads v as &v before activation
}
```

Without two-phase borrows this code would fail with E0502 ("cannot borrow `v` as immutable because it is also borrowed as mutable"). Two-phase exists **only** for method-call argument expressions. It does NOT help in patterns like `let x = &mut v; v.len();` (E0502 there).

NEVER rely on two-phase outside method-call argument position. If you need shared+exclusive access in arbitrary order, restructure the code.

---

## Reborrowing: passing a `&mut` without moving it

A reborrow creates a **new** `&mut` (or `&`) borrow from an existing one. Syntactically: `&mut *r`. Most reborrows are **implicit**: when you pass a `&mut T` to a function that takes `&mut T`, the compiler reborrows for you.

```rust
fn takes_mut(s: &mut String) { s.push('!'); }

fn reborrow_demo() {
    let mut s = String::from("hi");
    let r: &mut String = &mut s;
    takes_mut(r);          // implicit reborrow: takes_mut gets &mut *r
    takes_mut(r);          // original `r` still usable
    r.push('?');
    println!("{r}");       // "hi!!?"
}
```

ALWAYS think of method calls on a `&mut T` as receiving an implicit reborrow. The original `&mut` remains live for use after the call.

Explicit reborrow is needed when:

- Returning a `&mut` derived from another `&mut` whose lifetime the compiler cannot infer.
- Stashing a `&mut` into a generic context that does not perform auto-reborrow (some closure / trait-object cases).

```rust
fn get_mut<'a>(opt: &'a mut Option<String>) -> Option<&'a mut String> {
    opt.as_mut()           // as_mut() already reborrows; nothing extra needed
}
```

---

## Split borrows: different fields, different references

The compiler tracks borrow paths at the **field level**. You can borrow disjoint fields of `&mut self` independently:

```rust
struct Pair { a: String, b: String }

fn split_struct(p: &mut Pair) {
    let ra = &mut p.a;
    let rb = &mut p.b;
    ra.push('!');
    rb.push('?');
}
```

This compiles because `p.a` and `p.b` are disjoint paths. However:

- ALWAYS write the borrows as `&mut p.a` and `&mut p.b` directly. Going through a helper method that returns `&mut self.a` collapses the borrow to the **whole** struct (E0499 on the next field access).
- NEVER expect this through a `Vec` index expression: `&mut v[0]` and `&mut v[1]` both call `IndexMut::index_mut(&mut self, _)`, which borrows the whole `Vec`. Use `split_at_mut` (below) instead.

For slice / `Vec` access:

```rust
fn split_slice(v: &mut [i32]) {
    let (left, right) = v.split_at_mut(1);
    left[0] = 10;
    right[0] = 20;
}
```

`split_at_mut(idx)` returns two non-overlapping `&mut [T]` halves and is the standard pattern for two-mutable-pieces-of-one-Vec. For more partitions use `chunks_mut`, `split_first_mut`, `iter_mut`.

---

## Interior mutability: when "&T but mutate" is genuinely needed

Interior mutability is the **sound escape hatch** that lets you mutate through a shared reference. All sound primitives are built on `UnsafeCell<T>`, the only type the compiler trusts to allow this.

Pick interior mutability ONLY when you have proven that:

1. The borrow rules cannot be expressed at compile-time for this design (typical: observer pattern, shared cache, callback registries).
2. A simpler restructure (split borrow, NLL-friendly scoping, returning indices instead of references) does not work.

### Cell<T>: zero-cost, `Copy` (or move in/out)

`Cell<T>` does not hand out references to its contents. You can only **move values in and out**, or **read by copy** if `T: Copy`. No runtime check, no overhead.

```rust
use std::cell::Cell;

let c = Cell::new(0_i32);
c.set(c.get() + 1);          // T: Copy path
let c2 = Cell::new(String::from("hi"));
let s = c2.replace(String::from("bye"));   // move out, put new in
```

ALWAYS prefer `Cell` over `RefCell` when `T: Copy` or when you only need swap semantics. `Cell` has **zero** runtime cost.

NEVER try to take `&T` or `&mut T` out of a `Cell<T>`: the API is intentionally designed not to expose them.

### RefCell<T>: runtime-checked borrows

`RefCell<T>` hands out `Ref<'_, T>` and `RefMut<'_, T>` smart pointers and tracks the borrow state at runtime. **Panics** if the borrow rules are violated.

```rust
use std::cell::RefCell;

let r = RefCell::new(vec![1, 2, 3]);

{
    let v = r.borrow();
    println!("{}", v.len());     // immutable borrow, OK
}                                // borrow ends here

{
    let mut v = r.borrow_mut();
    v.push(4);                   // exclusive borrow, OK
}                                // borrow ends here
```

Panic scenarios:

```rust
let r = RefCell::new(0);
let _a = r.borrow();
let _b = r.borrow_mut();         // PANIC at runtime: already borrowed
```

The borrow tokens released when `Ref` / `RefMut` drop. NEVER hold a `RefCell` borrow across a function call that might re-enter and borrow the same cell: you will panic.

ALWAYS use `try_borrow()` / `try_borrow_mut()` in code paths where re-entry is plausible.

### Mutex<T>: thread-safe equivalent of RefCell

`Mutex<T>` provides interior mutability across threads. Acquiring the lock blocks until the previous holder releases it.

```rust
use std::sync::Mutex;

let m = Mutex::new(0_u32);
{
    let mut guard = m.lock().unwrap();
    *guard += 1;
}    // guard drops, lock released
```

- `Mutex<T>: Send + Sync` whenever `T: Send`.
- A `MutexGuard<'_, T>` is `!Send`: the thread that locked must unlock.
- Holding a `std::sync::Mutex` guard across `.await` is a deadlock-prone bug (Tokio runtime can move the task to another thread). Use `tokio::sync::Mutex` in async code. See [[rust-errors-async]].

NEVER use `std::sync::Mutex` in async code that holds the guard across `.await`. ALWAYS use `tokio::sync::Mutex` there.

---

## Decision tree: which reference / mutability to use

```
Does the callee need to OWN the value (consume, store, or transfer it)?
|
+-- Yes: take T (owned). Caller is moving in.
|
+-- No: does the callee MUTATE?
    |
    +-- Yes: take &mut T. (Only one caller at a time.)
    |   |
    |   +-- Need shared writes? Move to one of:
    |       * Cell<T>     if T: Copy or swap-only semantics, single-thread.
    |       * RefCell<T>  if runtime borrow tracking is acceptable, single-thread.
    |       * Mutex<T>    if multi-thread / Sync needed.
    |       * AtomicX     if T is a primitive integer/bool/pointer.
    |
    +-- No: take &T. (Many callers welcome.)
        |
        +-- T is `String`?    Take &str instead.
        +-- T is `Vec<X>`?    Take &[X] instead.
        +-- T is `Box<dyn X>`? Take &dyn X instead.
        +-- T is `PathBuf`?   Take &Path instead.
```

ALWAYS take the **most flexible** unsized borrow (`&str`, `&[T]`, `&Path`, `&dyn X`) over the owned reference (`&String`, `&Vec<T>`, `&PathBuf`, `&Box<dyn X>`). Clippy enforces this via `ptr_arg`.

---

## Common error decoder

| Code | Plain English | First thing to try |
|------|---------------|-------------------|
| E0502 | "borrowed as mutable while also borrowed as immutable" | Shorten the immutable borrow scope; rely on NLL. |
| E0499 | "borrowed as mutable more than once" | Use split borrows, `split_at_mut`, or restructure into two scopes. |
| E0596 | "cannot borrow as mutable, not declared mut" | Add `mut` to the binding, or take `&mut` instead of `&`. |
| E0507 | "cannot move out of borrowed content" | Match `&pattern` to bind by reference, or use `std::mem::replace`. |
| E0382 | "borrow of moved value" | The value was moved; clone or pass by reference upstream. |

For each E-code see [[rust-errors-borrow-checker]] for full decoding.

---

## Avoid these mistakes

(Full list with WHYs in `references/anti-patterns.md`.)

- Reaching for `.clone()` to silence E0502 instead of letting NLL end the borrow.
- Using `RefCell` to "work around" the borrow checker when a scope restructure would suffice.
- Holding a `RefCell::borrow()` across a function call that may recursively borrow.
- Trying `split_at_mut`-like tricks on arbitrary struct fields (use destructuring or `&mut self.x` directly).
- Using `Cell<T>` for non-`Copy` types and expecting `get()` / `set()` semantics (it does not; you must use `replace` / `take` / `swap`).
- Holding `std::sync::MutexGuard` across `.await` (use `tokio::sync::Mutex`).
- Returning `&mut self.field` from a method that you want to call repeatedly while the borrow is live (E0499 the second time).
- Calling `as_mut()` on `Option<T>` and then `as_ref()` on the same option in the same scope expecting them to coexist.

---

## Reference links

[book-borrow]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
[ref-cell]: https://doc.rust-lang.org/std/cell/index.html
[refcell]: https://doc.rust-lang.org/std/cell/struct.RefCell.html
[cell]: https://doc.rust-lang.org/std/cell/struct.Cell.html
[mutex]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
[nomicon-alias]: https://doc.rust-lang.org/nomicon/aliasing.html
[split-at-mut]: https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut
[unsafe-cell]: https://doc.rust-lang.org/std/cell/struct.UnsafeCell.html

- [Rust Book Ch 4.2: References and Borrowing][book-borrow]
- [`std::cell` overview][ref-cell]
- [`std::cell::Cell`][cell]
- [`std::cell::RefCell`][refcell]
- [`std::cell::UnsafeCell`][unsafe-cell]
- [`std::sync::Mutex`][mutex]
- [`slice::split_at_mut`][split-at-mut]
- [Rustonomicon: Aliasing][nomicon-alias]

For deeper drill-downs see:

- `references/methods.md`: complete API signatures for `&T`, `&mut T`, `Cell`, `RefCell`, `Mutex`, slice splitters.
- `references/examples.md`: working code examples for NLL, two-phase, reborrow, split borrows, interior mutability.
- `references/anti-patterns.md`: 10+ documented anti-patterns with WHY and FIX.

