rust-mutability
SKILL.md
Mutability Types
| Type | Controller | Thread-Safe | Use Case |
|---|---|---|---|
&mut T |
External caller | Yes | Standard mutable borrow |
Cell<T> |
Interior | No | Copy types with interior mutability |
RefCell<T> |
Interior | No | Non-Copy types with interior mutability |
Mutex<T> |
Interior | Yes | Multi-threaded interior mutability |
RwLock<T> |
Interior | Yes | Multi-threaded read-write lock |
Solution Patterns
Pattern 1: External Mutability
// Standard mutable borrow
fn increment(counter: &mut u32) {
*counter += 1;
}
// Mutable method
impl Counter {
fn increment(&mut self) {
self.value += 1;
}
}
When to use: Default choice, mutability controlled by caller.
Pattern 2: Cell for Copy Types
use std::cell::Cell;
struct State {
count: Cell<u32>,
}
impl State {
// Get immutable &self, mutate interior
fn increment(&self) {
self.count.set(self.count.get() + 1);
}
}
When to use: Simple values (Copy types) need interior mutability.
Trade-offs: Only works with Copy types, no references.
Pattern 3: RefCell for Non-Copy Types
use std::cell::RefCell;
struct Cache {
data: RefCell<HashMap<String, Value>>,
}
impl Cache {
fn insert(&self, key: String, value: Value) {
self.data.borrow_mut().insert(key, value);
}
fn get(&self, key: &str) -> Option<Value> {
self.data.borrow().get(key).cloned()
}
}
When to use: Need &mut T from &self, single-threaded.
Trade-offs: Runtime borrow checking, can panic.
Pattern 4: Mutex for Thread Safety
use std::sync::Mutex;
struct SharedState {
data: Mutex<HashMap<String, Value>>,
}
impl SharedState {
fn insert(&self, key: String, value: Value) {
self.data.lock().unwrap().insert(key, value);
}
}
When to use: Multi-threaded interior mutability.
Trade-offs: Lock contention, can deadlock.
Pattern 5: RwLock for Read-Heavy Workloads
use std::sync::RwLock;
struct Config {
settings: RwLock<HashMap<String, String>>,
}
impl Config {
fn get(&self, key: &str) -> Option<String> {
self.settings.read().unwrap().get(key).cloned()
}
fn update(&self, key: String, value: String) {
self.settings.write().unwrap().insert(key, value);
}
}
When to use: Many readers, few writers.
Trade-offs: Write locks more expensive than Mutex.
Borrow Rules
At any time, you can have either:
├─ Multiple &T (immutable borrows)
└─ OR one &mut T (mutable borrow)
Never both simultaneously
Error Code Quick Reference
| Code | Meaning | Don't Say | Ask Instead |
|---|---|---|---|
| E0596 | Cannot get mutable reference | "add mut" | Does this really need mutability? |
| E0499 | Multiple mutable borrows conflict | "split borrows" | Is data structure design correct? |
| E0502 | Borrow conflict | "separate scopes" | Why both borrows needed simultaneously? |
| RefCell panic | Runtime borrow error | "use try_borrow" | Is runtime checking appropriate? |
Workflow
Step 1: Choose Mutability Strategy
Single-threaded?
Need &mut from &self?
→ RefCell<T>
Copy type?
→ Cell<T>
Otherwise?
→ &mut T
Multi-threaded?
Simple atomic?
→ AtomicU64/AtomicBool
Complex data?
Read-heavy → RwLock<T>
Write-heavy → Mutex<T>
Step 2: Handle Borrow Conflicts
E0499 (multiple mut borrows)?
→ Split struct into smaller pieces
→ Use Cell/RefCell for interior mutability
→ Redesign to avoid simultaneous access
E0502 (borrow conflict)?
→ Minimize borrow scopes
→ Clone data if needed
→ Restructure code flow
Step 3: Consider Trade-offs
RefCell?
✅ Flexible
❌ Runtime panics possible
→ Use in prototypes, single-threaded
Mutex?
✅ Thread-safe
❌ Lock contention
→ Profile before optimizing
RwLock?
✅ Many readers efficient
❌ Writer starvation possible
→ Use when reads >> writes
Thread-Safe Selection
Atomic Types
use std::sync::atomic::{AtomicU64, Ordering};
let counter = AtomicU64::new(0);
counter.fetch_add(1, Ordering::Relaxed);
Use when: Simple counters, flags.
Mutex
use std::sync::Mutex;
let data = Mutex::new(HashMap::new());
data.lock().unwrap().insert(key, value);
Use when: Thread-safe mutation, balanced read/write.
RwLock
use std::sync::RwLock;
let data = RwLock::new(HashMap::new());
data.read().unwrap().get(&key); // Many readers
data.write().unwrap().insert(key, value); // Few writers
Use when: Read-heavy workloads (10+ reads per write).
Common Pitfalls
1. Borrow Conflict
Symptom: E0499, E0502 errors
// ❌ Bad: multiple mutable borrows
let r1 = &mut data.field1;
let r2 = &mut data.field2; // Error!
// ✅ Good: split borrows
let (field1, field2) = (&mut data.field1, &mut data.field2);
// ✅ Better: restructure
struct Data {
part1: Part1,
part2: Part2,
}
2. RefCell Panic
Symptom: "already borrowed" panic at runtime
// ❌ Bad: nested borrows
let cell = RefCell::new(vec![1, 2, 3]);
let borrow1 = cell.borrow();
let borrow2 = cell.borrow_mut(); // Panics!
// ✅ Good: drop first borrow
{
let borrow1 = cell.borrow();
// use borrow1...
} // dropped
let borrow2 = cell.borrow_mut(); // OK
// ✅ Better: use try_borrow
if let Ok(mut b) = cell.try_borrow_mut() {
// safe mutation
}
3. Lock Held Across Await
Symptom: Deadlock in async code
// ❌ Bad: MutexGuard across await
let guard = mutex.lock().unwrap();
async_op().await; // DANGER
// ✅ Good: drop lock before await
let value = {
let guard = mutex.lock().unwrap();
guard.clone()
}; // lock dropped
async_op().await;
Review Checklist
When reviewing mutability code:
- Mutability truly necessary (not premature)
- Appropriate mutability type chosen (Cell/RefCell/Mutex)
- RefCell used only in single-threaded contexts
- Mutex/RwLock used for multi-threaded access
- Lock scopes minimized to avoid contention
- No locks held across
.awaitpoints - Borrow conflicts resolved at design level
- Runtime panics handled (try_borrow)
- Atomic types used for simple counters/flags
- Read-write patterns match RwLock choice
Verification Commands
# Check compilation
cargo check
# Look for borrow conflict errors
cargo check 2>&1 | grep -E "E0499|E0502|E0596"
# Run tests
cargo test
# Check for deadlocks (with loom)
cargo test --features loom
# Clippy warnings
cargo clippy -- -W clippy::mutex_atomic
Advanced Patterns
Splitting Borrows
// ✅ Split struct to enable simultaneous borrows
struct Data {
readers: Vec<Reader>,
writers: Vec<Writer>,
}
fn process(data: &mut Data) {
let readers = &data.readers;
let writers = &mut data.writers; // OK, different fields
// use both...
}
Interior Mutability with Shared Ownership
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct Shared {
inner: Arc<Mutex<Inner>>,
}
impl Shared {
fn update(&self) {
self.inner.lock().unwrap().modify();
}
}
Related Skills
- rust-ownership - Ownership and borrowing fundamentals
- rust-concurrency - Thread-safe patterns
- rust-unsafe - UnsafeCell and low-level mutability
- rust-anti-pattern - Mutability anti-patterns
- rust-performance - Lock contention optimization
Localized Reference
- Chinese version: SKILL_ZH.md - 完整中文版本,包含所有内容
Weekly Installs
7
Repository
huiali/rust-skillsGitHub Stars
20
First Seen
Jan 28, 2026
Security Audits
Installed on
gemini-cli6
claude-code4
github-copilot4
amp4
cline4
codex4