# Rust Core Memory Model

> Use when the user needs the conceptual overview of Rust memory: ownership, move vs copy vs clone, drop semantics, RAII, stack vs heap, smart pointer choice (Box / Rc / Arc), interior mutability (Cell / RefCell / Mutex), or Send/Sync auto-traits. Prevents misusing clone() to silence the borrow checker, picking Arc<Mutex> when atomic suffices, treating Rc as thread-safe, or forgetting that Drop runs even on panic. Covers: ownership rules, move semantics, Copy/Clone/Drop traits, RAII, stack vs heap, niche optimization mention, Box/Rc/Arc semantic differences, Cell/RefCell/UnsafeCell, Send/Sync overview. Keywords: ownership, move, copy, clone, drop, RAII, stack vs heap, Box, Rc, Arc, RefCell, Cell, Mutex, Send, Sync, interior mutability, "why do I need to clone", "moved value error", "cannot borrow", memory layout, "double free", "what is Drop", smart pointer, reference counting, ownership transfer, "what does &str cost", auto trait.

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

---


# rust-core-memory-model

Conceptual overview of Rust's memory model. This skill teaches the **mental model**: ownership, move semantics, RAII, stack vs heap, smart pointer choice, interior mutability, and the Send/Sync auto-traits. Granular mechanics live in cross-referenced skills.

Cross-references: [[rust-syntax-ownership]] [[rust-syntax-borrowing]] [[rust-syntax-smart-pointers]] [[rust-syntax-async-await]]

---

## When to use this skill

- User asks "what is ownership / moves / clones / drops in Rust"
- User confused by a "value moved here" or "cannot borrow" compiler message at the **conceptual** level
- User asks "should I use `Rc` or `Arc`", "do I need `Mutex` or `AtomicX`", "what is interior mutability"
- User asks "is this `Send`? is this `Sync`?"
- User asks "what gets dropped when, and in what order"
- User asks "what does `&str` cost vs `String`" or general stack-vs-heap questions

For the **mechanics** of these (exact syntax, lifetime elision, reborrow rules, smart pointer methods), refer the user to the cross-referenced skills above.

---

## Core rules (verbatim from the Book)

The three ownership rules are:

1. Each value in Rust has an **owner**.
2. There can only be **one owner at a time**.
3. When the owner goes out of scope, the value is **dropped**.

ALWAYS state these three rules first when explaining ownership. They are the foundation; everything else follows.

The two reference invariants:

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

---

## Quick reference table

| Concept | One-line rule |
|---------|---------------|
| Move | Assigning a non-`Copy` value transfers ownership; the source becomes invalid. |
| `Copy` | Implicit bit-copy on assignment. Forbidden alongside `Drop`. All fields must be `Copy`. |
| `Clone` | Explicit `.clone()`. Supertrait of `Copy`. Can do deep copy. |
| `Drop` | `fn drop(&mut self)` runs when owner goes out of scope. Cannot be called directly (E0040). |
| Stack | Fixed-size, LIFO, fast. Holds owners, references, `Copy` values. |
| Heap | Dynamic-size, allocator-managed, slower. Holds `Box<T>`, `Vec<T>`, `String`, `Rc<T>`, `Arc<T>` content. |
| `Box<T>` | Single owner on the heap. Zero-cost over raw alloc. |
| `Rc<T>` | Shared ownership, **single-threaded** (`!Send + !Sync`). Non-atomic refcount. |
| `Arc<T>` | Shared ownership, **thread-safe**. Atomic refcount, more expensive than `Rc`. |
| `Cell<T>` | Interior mutability for `Copy` (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` | Auto-trait: type can be **moved** across threads. |
| `Sync` | Auto-trait: `&T` can be **shared** across threads. |

---

## Decision tree A: move vs borrow vs clone

```
Need to pass a value to a function/scope?
|
+-- Will the original be used again after the call?
|   |
|   +-- YES -> borrow (& for read, &mut for write)
|   |
|   +-- NO  -> move (just pass it; ownership transfers)
|
+-- Did the borrow checker reject your code?
    |
    +-- Do NOT default to .clone() to "fix" it.
    +-- Restructure first: scope the borrow, split the data,
    |   return an owned value, or take ownership in.
    +-- ONLY clone() if:
        - The type is cheap to clone (small Copy-like, Arc<T>, &str -> String for owned API),
        - OR you genuinely need two independent owners.
```

ALWAYS reach for borrowing before cloning. NEVER use `.clone()` as a borrow-checker workaround; it masks the design issue (Clippy lint: `redundant_clone`).

---

## Decision tree B: Box vs Rc vs Arc

```
Do you need shared ownership (multiple owners of the SAME allocation)?
|
+-- NO  -> Box<T>
|          (single owner, heap allocation, zero runtime cost over raw alloc)
|
+-- YES -> Will any owner live on a DIFFERENT thread?
           |
           +-- NO  -> Rc<T>
           |          (non-atomic refcount, single-threaded only, !Send + !Sync)
           |
           +-- YES -> Arc<T>
                      (atomic refcount, thread-safe, more expensive than Rc)
```

NEVER reach for `Arc` when `Rc` works. Atomic operations are more expensive than ordinary memory accesses ([std::sync::Arc][arc-docs]).

NEVER attempt to send `Rc<T>` across threads; the compiler rejects it because `Rc<T>` is `!Send`. Trying is a sign your design needs rework, not a workaround.

---

## Decision tree C: Cell vs RefCell vs Mutex vs Atomic

```
Need to mutate behind a shared reference?
|
+-- Is the value `Copy` AND single-threaded?
|   -> Cell<T>          (no runtime cost; get/set/replace by value)
|
+-- Non-Copy, single-threaded, willing to enforce borrow rules at runtime?
|   -> RefCell<T>       (panics on borrow violation)
|
+-- Multi-threaded AND value is a simple integer/bool/pointer?
|   -> AtomicU32 / AtomicBool / AtomicPtr / etc.
|                       (lock-free; cheapest concurrency primitive)
|
+-- Multi-threaded AND non-trivial value or multi-step transaction?
|   -> Mutex<T>         (exclusive access; blocks contending threads)
|
+-- Multi-threaded, mostly-read workload?
    -> RwLock<T>        (many readers OR one writer)
```

ALWAYS prefer `AtomicX` over `Mutex<X>` when the value fits an atomic primitive. Lock acquisition costs more than a single atomic instruction.

The only `Sync` types in `std::cell` are `UnsafeCell` and `SyncUnsafeCell`. `Cell`, `RefCell`, `OnceCell` are explicitly `!Sync`.

---

## Move semantics

Assigning a non-`Copy` value moves it; the source is invalidated **at compile time**:

```rust
let s1 = String::from("hi");
let s2 = s1;                  // s1 moved into s2
// println!("{s1}");          // E0382: value used after move
```

The same rule applies to function arguments and return values. Returning a value moves it to the caller.

ALWAYS think of the binding as the **owner**, not as the storage cell. The value moves; the type stays on the stack but its heap payload (if any) now belongs to the new owner.

---

## Copy vs Clone

`Copy` is **implicit, bit-by-bit, never overloadable**:

```rust
let x: i32 = 5;
let y = x;       // copy (i32 is Copy)
println!("{x} {y}");   // both valid
```

`Clone` is **explicit, can do anything safely**:

```rust
let a = String::from("hi");
let b = a.clone();        // deep copy (heap buffer duplicated)
```

Rules:

- `Copy` can ONLY be implemented for types whose fields are all `Copy`.
- `Copy` and `Drop` are **mutually exclusive**: a type implementing `Drop` can never be `Copy`.
- `Clone` is a **supertrait of `Copy`**: every `Copy` type also implements `Clone`. For a `Copy` type, `clone()` can be implemented as `*self`.

Primitive `Copy` types: all integers (`i8`...`u128`, `isize`/`usize`), floats (`f32`/`f64`), `bool`, `char`, `!`, function pointers, function items, shared references `&T` (regardless of `T`), raw pointers `*const T` / `*mut T`.

---

## Drop and RAII

`Drop` runs automatically when an owner goes out of scope. This is Rust's **RAII** (Resource Acquisition Is Initialization) guarantee:

```rust
pub trait Drop {
    fn drop(&mut self);
}
```

Rules:

- You **cannot call `.drop()` directly** (compile error E0040). Use `std::mem::drop(value)` to drop early.
- Drop runs **even during panic unwinding** (this is what makes `Mutex` poisoning, `File` closing, etc. correct).
- `Drop::drop` itself SHOULD NOT panic. A panic during unwind-drop becomes a **double panic** and aborts the program.

### Drop order

- **Local variables**: dropped in **reverse declaration order** (LIFO).
- **Struct fields, tuple elements, array elements**: dropped in **declaration order** (first-to-last).
- **Enum variants**: same as struct (the active variant's fields in declaration order).

ALWAYS rely on Drop for resource cleanup. NEVER write a separate `close()` method that the user must remember to call.

---

## Stack vs heap

Where data lives:

| Lives on stack | Lives on heap (behind a pointer) |
|----------------|----------------------------------|
| `i32`, `bool`, `char`, fixed arrays `[T; N]` | `Box<T>`, `Vec<T>`, `String`, `HashMap<K,V>` |
| References `&T`, `&mut T` | `Rc<T>`, `Arc<T>` payload |
| `Option<T>` and small enums | Trait objects `Box<dyn Trait>` |
| Tuples and structs of stack values | Anything you explicitly `Box::new` |

Performance reality (from the Book):

- The stack allocator never searches; it just bumps the pointer. Fast.
- Heap access requires following a pointer. Slower than stack access on modern CPUs (cache locality).

### Niche optimization

Rust's layout optimizer can use **invalid bit patterns** to encode `None` for free:

- `Option<&T>` is **the same size** as `&T` (references are non-null; null encodes `None`).
- `Option<Box<T>>`, `Option<NonZeroU32>`, `Option<NonNull<T>>`: same size as the inner type.

Example: `size_of::<Option<NonZeroU32>>() == 4`, same as `u32`.

ALWAYS pick `NonZeroU32` (and friends) when zero is invalid for your domain; you gain a free `Option` with no size overhead.

---

## Smart pointer overview

This is the conceptual surface only. See [[rust-syntax-smart-pointers]] for full method signatures and patterns.

### `Box<T>`

- Single owner.
- Heap-allocated.
- Privileged in one orphan-rule sense: a trait can be implemented for `Box<T>` in the same crate as `T`, which is not generally allowed for other generic types.
- Used for: large values you want off the stack, trait objects (`Box<dyn Trait>`), recursive types (a struct that contains itself must do so behind `Box`).

### `Rc<T>`

- Shared ownership via **non-atomic** reference count.
- `!Send` and `!Sync`. Compiler **rejects** sending an `Rc<T>` to another thread.
- Cycles leak (use `Weak<T>` to break them).
- Used for: graph-like single-threaded data, multiple owners of read-mostly data within one thread.

Style: both `rc.clone()` and `Rc::clone(&rc)` are valid. Some codebases prefer the associated-function form because it makes "I'm cloning the Rc, not the inner T" explicit. Both compile to the same code.

### `Arc<T>`

- Shared ownership via **atomic** reference count.
- `Send + Sync` when `T: Send + Sync`.
- More expensive than `Rc` (atomic ops vs plain memory access).
- Used for: shared state across threads, async tasks holding shared data, work-stealing executors.

`Arc<T>` is **immutable** by default. To mutate shared state across threads, combine: `Arc<Mutex<T>>`, `Arc<RwLock<T>>`, or `Arc<AtomicX>`.

---

## Interior mutability overview

The Rust language enforces "mutate only through `&mut`" statically. Interior mutability is the **sound escape hatch** built on `UnsafeCell<T>`.

- `UnsafeCell<T>` is the **only sound primitive** for mutating through a shared reference. The compiler reads this type and disables optimizations that would be wrong in the presence of aliased mutation.
- `Cell<T>`: single-threaded, no references handed out; values move in/out. Free at runtime.
- `RefCell<T>`: single-threaded, runtime-checked borrows. Panics on violation. Costs one refcount load/store per borrow.
- `Mutex<T>` / `RwLock<T>`: thread-safe equivalents of `RefCell`. Block on contention.
- `Atomic*`: thread-safe interior mutability for primitives. Lock-free.

ALWAYS pick the cheapest tool that works: `Cell` over `RefCell` if `Copy`; `AtomicX` over `Mutex` if primitive; `RwLock` over `Mutex` only if reads dominate.

---

## Send and Sync auto-traits

Both `Send` and `Sync` are **auto-traits**: the compiler implements them automatically when all fields qualify.

- `Send`: it is safe to **transfer ownership** to another thread.
- `Sync`: it is safe to **share `&T` with** another thread. Equivalently: `T: Sync` iff `&T: Send`.

Auto-implementation rules:

- `&T`, `&mut T`, `*const T`, `*mut T`, `[T; n]`, `[T]` implement `Send`/`Sync` if `T` does. **Exception**: `*mut T` and `*const T` have a negative `Send` and `Sync` impl (raw pointers are opt-in concurrency-safe).
- Function items and function pointers automatically implement both.
- Structs, enums, unions, tuples implement them if **all fields** do.
- Closures implement them if the **captures** do.

Common `!Send` types: `Rc<T>`, `RefCell<T>` (`!Sync`), `MutexGuard<'_, T>` (`!Send`, must be released on the thread that locked).

ALWAYS let the compiler decide. NEVER `unsafe impl Send` unless you have audited the type for thread safety against the soundness conditions in the Rustonomicon.

---

## Section: Avoid these mistakes

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

- Slapping `.clone()` everywhere to silence the borrow checker.
- Reaching for `Arc<Mutex<T>>` when an `AtomicU64` would do.
- Trying to send `Rc<T>` across threads (won't compile, but the urge means the design is wrong).
- Implementing `Drop` and `Copy` on the same type (impossible by language rule, but worth knowing).
- Returning a reference to a function-local value (E0515: borrowed value does not live long enough).
- Calling `.drop(&mut value)` directly (E0040).

---

## Reference links

[ownership]: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html
[borrowing]: https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html
[copy-trait]: https://doc.rust-lang.org/std/marker/trait.Copy.html
[clone-trait]: https://doc.rust-lang.org/std/clone/trait.Clone.html
[drop-trait]: https://doc.rust-lang.org/std/ops/trait.Drop.html
[special-traits]: https://doc.rust-lang.org/reference/special-types-and-traits.html
[arc-docs]: https://doc.rust-lang.org/std/sync/struct.Arc.html
[rc-docs]: https://doc.rust-lang.org/std/rc/struct.Rc.html
[cell-docs]: https://doc.rust-lang.org/std/cell/index.html

- [Rust Book Ch 4.1: What is Ownership?][ownership]
- [Rust Book Ch 4.2: References and Borrowing][borrowing]
- [`std::marker::Copy`][copy-trait]
- [`std::clone::Clone`][clone-trait]
- [`std::ops::Drop`][drop-trait]
- [Rust Reference: Special Types and Traits][special-traits] (`Send`, `Sync`, `Sized`, `Copy`)
- [`std::sync::Arc`][arc-docs]
- [`std::rc::Rc`][rc-docs]
- [`std::cell`][cell-docs]

For deeper drill-downs see:
- `references/methods.md`: trait signatures, smart pointer signatures, marker traits.
- `references/examples.md`: complete working code examples.
- `references/anti-patterns.md`: common mistakes with root-cause explanations.

