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.
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.
license
MIT
compatibility
Designed for Claude Code. Requires Rust 1.85+, edition 2024.
metadata
{"author":"OpenAEC-Foundation","version":"1.0"}
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.
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:
Each value in Rust has an owner.
There can only be one owner at a time.
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).
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).
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:
lets1 = String::from("hi");
lets2 = 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:
letx: i32 = 5;
lety = x; // copy (i32 is Copy)println!("{x} {y}"); // both valid
Clone is explicit, can do anything safely:
leta = String::from("hi");
letb = 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:
pubtraitDrop {
fndrop(&mutself);
}
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).