Interior Mutability Patterns
Interior mutability lets you mutate data through shared references when runtime borrow rules or synchronization replace compile-time exclusivity.
Search across all documentation pages
Interior mutability lets you mutate data through shared references when runtime borrow rules or synchronization replace compile-time exclusivity.
use std::cell::RefCell;
let cache = RefCell::new(HashMap::new());
cache.borrow_mut().insert("k", 1);When to reach for this: Graphs with aliasing, single-thread caches, or shared counters with Mutex/Atomic.
use std::sync::{Arc, Mutex};
struct AppState {
counter: Mutex<u64>,
}
async fn handler(state: Arc<AppState>) {
let mut n = state.counter.lock().unwrap();
*n += 1;
}What this demonstrates:
RefCell for single-thread interior mut (runtime borrow panic if violated)Mutex for multi-thread shared mutationCell for Copy types onlyArc<Mutex<T>> common in Axum shared state (prefer tokio::sync::Mutex in async)| Type | Threads | Panic on conflict |
|---|---|---|
Cell | No | No (Copy only) |
RefCell | No | Yes (borrow) |
Mutex | Yes | Poison error |
RwLock | Yes | Poison error |
Atomic* | Yes | No |
Prefer message passing or owned mutation before Rc<RefCell<_>>.
tokio::sync::Mutex; lock scope minimal, no await while holding std Mutex.lock().expect("msg") with policy.Arc shared across threads.| Alternative | Use When | Don't Use When |
|---|---|---|
Owned mut | Single owner | Shared cache needed |
| Channels | Producer/consumer | Fine-grained shared state |
DashMap | Concurrent map | Simple single-thread |
RefCell cheaper; no threads involved.
tokio Mutex await-friendly; std Mutex blocks executor if held across await.
&self methods mutating inner Mutex is standard pattern.
Yes for single-thread toggles.
Lazy init without mut ref after init; different pattern.
Possible for AST; consider arena if performance matters.
RefCell not Sync; cannot share across threads in Arc without Mutex.
Warn on std Mutex in async contexts; use tokio mutex.
#[should_panic] on double borrow_mut in tests.
Counters and flags without protecting large structs.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 16, 2026