m03-mutability
actionbook/rust-skills
Resolve Rust mutability conflicts by understanding ownership, borrowing rules, and interior mutability patterns.
What is m03-mutability?
Addresses E0596, E0499, E0502 and related borrow-checker errors. Use this skill when you encounter mutability conflicts, need to decide between &mut, Cell, RefCell, Mutex, or RwLock, or when borrow conflicts suggest a data-structure redesign.
- Diagnose mutability errors (E0596, E0499, E0502) and trace root causes
- Choose appropriate interior mutability pattern (Cell, RefCell, Mutex, RwLock, Atomic) based on thread context and data type
- Restructure code to satisfy borrow rules without unnecessary runtime overhead
- Identify when borrow conflicts signal deeper design issues in data structures
- Evaluate trade-offs between compile-time (&mut) and runtime (RefCell) borrow checking
How to install m03-mutability
npx skills add https://github.com/actionbook/rust-skills --skill m03-mutabilityHow to use m03-mutability
- 1.Identify the specific mutability error (E0596, E0499, E0502, or RefCell panic)
- 2.Ask the core question: Why does this data need to change, and who should control mutation?
- 3.Determine thread context: single-threaded or multi-threaded
- 4.Select the appropriate pattern from the decision table (Cell, RefCell, Mutex, RwLock, or Atomic)
- 5.If borrow conflicts persist, trace up to m09-domain to evaluate data-structure design
Use cases
- Fixing 'cannot borrow as mutable' errors by selecting the right mutability mechanism
- Resolving multiple-borrow conflicts by restructuring ownership or using interior mutability
- Choosing between Cell and RefCell for single-threaded mutable state
- Selecting Mutex vs RwLock for thread-safe shared mutation based on read/write patterns
- Detecting when RefCell panics indicate a need for data-structure redesign rather than error handling
- Rust developers encountering borrow-checker errors
- Engineers designing shared mutable state in single-threaded or concurrent code
- Teams refactoring code to reduce borrow conflicts
m03-mutability FAQ
Use Cell<T> for Copy types with zero runtime cost; use RefCell<T> for non-Copy types that need runtime borrow checking. Both are single-threaded only.
Mutex allows one writer at a time; RwLock allows multiple concurrent readers or one writer. Use RwLock when reads vastly outnumber writes.
RefCell defers borrow checking to runtime, allowing interior mutability through &self. Panics indicate a logic error; consider restructuring instead of just handling the panic.
If conflicts persist after trying different borrow patterns, check m09-domain: the data may need to be split into separate fields or types with clearer ownership.
No; use async-aware locks (tokio::sync::Mutex) instead. Regular Mutex can block executor threads. See m07-concurrency for details.
Full instructions (SKILL.md)
Source of truth, from actionbook/rust-skills.
name: m03-mutability description: "CRITICAL: Use for mutability issues. Triggers: E0596, E0499, E0502, cannot borrow as mutable, already borrowed as immutable, mut, &mut, interior mutability, Cell, RefCell, Mutex, RwLock, 可变性, 内部可变性, 借用冲突" user-invocable: false
Mutability
Layer 1: Language Mechanics
Core Question
Why does this data need to change, and who can change it?
Before adding interior mutability, understand:
- Is mutation essential or accidental complexity?
- Who should control mutation?
- Is the mutation pattern safe?
Error → Design Question
| Error | Don't Just Say | Ask Instead |
|---|---|---|
| E0596 | "Add mut" | Should this really be mutable? |
| E0499 | "Split borrows" | Is the data structure right? |
| E0502 | "Separate scopes" | Why do we need both borrows? |
| RefCell panic | "Use try_borrow" | Is runtime check appropriate? |
Thinking Prompt
Before adding mutability:
-
Is mutation necessary?
- Maybe transform → return new value
- Maybe builder → construct immutably
-
Who controls mutation?
- External caller →
&mut T - Internal logic → interior mutability
- Concurrent access → synchronized mutability
- External caller →
-
What's the thread context?
- Single-thread → Cell/RefCell
- Multi-thread → Mutex/RwLock/Atomic
Trace Up ↑
When mutability conflicts persist:
E0499/E0502 (borrow conflicts)
↑ Ask: Is the data structure designed correctly?
↑ Check: m09-domain (should data be split?)
↑ Check: m07-concurrency (is async involved?)
| Persistent Error | Trace To | Question |
|---|---|---|
| Repeated borrow conflicts | m09-domain | Should data be restructured? |
| RefCell in async | m07-concurrency | Is Send/Sync needed? |
| Mutex deadlocks | m07-concurrency | Is the lock design right? |
Trace Down ↓
From design to implementation:
"Need mutable access from &self"
↓ T: Copy → Cell<T>
↓ T: !Copy → RefCell<T>
"Need thread-safe mutation"
↓ Simple counters → AtomicXxx
↓ Complex data → Mutex<T> or RwLock<T>
"Need shared mutable state"
↓ Single-thread: Rc<RefCell<T>>
↓ Multi-thread: Arc<Mutex<T>>
Borrow Rules
At any time, you can have EITHER:
├─ Multiple &T (immutable borrows)
└─ OR one &mut T (mutable borrow)
Never both simultaneously.
Quick Reference
| Pattern | Thread-Safe | Runtime Cost | Use When |
|---|---|---|---|
&mut T | N/A | Zero | Exclusive mutable access |
Cell<T> | No | Zero | Copy types, no refs needed |
RefCell<T> | No | Runtime check | Non-Copy, need runtime borrow |
Mutex<T> | Yes | Lock contention | Thread-safe mutation |
RwLock<T> | Yes | Lock contention | Many readers, few writers |
Atomic* | Yes | Minimal | Simple types (bool, usize) |
Error Code Reference
| Error | Cause | Quick Fix |
|---|---|---|
| E0596 | Borrowing immutable as mutable | Add mut or redesign |
| E0499 | Multiple mutable borrows | Restructure code flow |
| E0502 | &mut while & exists | Separate borrow scopes |
Interior Mutability Decision
| Scenario | Choose |
|---|---|
| T: Copy, single-thread | Cell<T> |
| T: !Copy, single-thread | RefCell<T> |
| T: Copy, multi-thread | AtomicXxx |
| T: !Copy, multi-thread | Mutex<T> or RwLock<T> |
| Read-heavy, multi-thread | RwLock<T> |
| Simple flags/counters | AtomicBool, AtomicUsize |
Anti-Patterns
| Anti-Pattern | Why Bad | Better |
|---|---|---|
| RefCell everywhere | Runtime panics | Clear ownership design |
| Mutex for single-thread | Unnecessary overhead | RefCell |
| Ignore RefCell panic | Hard to debug | Handle or restructure |
| Lock inside hot loop | Performance killer | Batch operations |
Related Skills
| When | See |
|---|---|
| Smart pointer choice | m02-resource |
| Thread safety | m07-concurrency |
| Data structure design | m09-domain |
| Anti-patterns | m15-anti-pattern |
Related skills
More from actionbook/rust-skills and the wider catalog.

m04-zero-cost
Master generics, traits, and zero-cost abstraction—choose static vs. dynamic dispatch correctly.

m05-type-driven
Use Rust's type system to make invalid states unrepresentable at compile time.

m06-error-handling
Master Rust error handling: when to use Result, Option, panic, and how to propagate errors effectively.

m07-concurrency
Master Rust concurrency: threads, async/await, channels, and thread-safe primitives.

m09-domain
Model domain concepts as Entities, Value Objects, and Aggregates using Rust ownership and type safety.

m10-performance
Measure, identify bottlenecks, then optimize with data structures, allocation strategies, and parallelism.