Interior mutability expert covering Cell, RefCell, Mutex, RwLock patterns, borrow conflicts (E0596, E0499, E0502), and thread-safe mutation strategies.
| 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 |
// 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.
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.
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.
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.
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.
At any time, you can have either:
├─ Multiple &T (immutable borrows)
└─ OR one &mut T (mutable borrow)
Never both simultaneously
| 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? |
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>
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
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
use std::sync::atomic::{AtomicU64, Ordering};
let counter = AtomicU64::new(0);
counter.fetch_add(1, Ordering::Relaxed);
Use when: Simple counters, flags.
use std::sync::Mutex;
let data = Mutex::new(HashMap::new());
data.lock().unwrap().insert(key, value);
Use when: Thread-safe mutation, balanced read/write.
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).
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,
}
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
}
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;
When reviewing mutability code:
.await points# 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
// ✅ 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...
}
use std::sync::{Arc, Mutex};
#[derive(Clone)]
struct Shared {
inner: Arc<Mutex<Inner>>,
}
impl Shared {
fn update(&self) {
self.inner.lock().unwrap().modify();
}
}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer