RefCell<T>
std::cell::RefCell<T> is a single-threaded interior mutability container that enforces Rust's borrowing rules (many &T XOR one &mut T) at runtime rather than at compile time. Unlike Cell<T>, which moves or copies values by value, RefCell<T> hands out smart pointer guards (Ref<'_, T> and RefMut<'_, T>) that dereference directly to &T and &mut T.
Memory Layout and Runtime Borrow Counter
A RefCell<T> stores a signed word-sized borrow counter (BorrowFlag, isize) alongside UnsafeCell<T>:
borrow == 0: Currently unborrowed.borrow > 0: Number of active sharedRef<'_, T>guards.borrow < 0(-1for a whole-cell mutable borrow): An active exclusiveRefMut<'_, T>guard exists.
RefCell<Vec<i32>> Memory Layout (32 bytes on 64-bit targets):
+-----------------------------------+-----------------------------------+
| borrow: Cell<isize> (8 bytes) | value: UnsafeCell<Vec<i32>> (24B) |
| 0 -> unlocked | ptr, len, cap |
| >0 -> N shared Ref<T> guards | |
| <0 -> active RefMut<T> guard | |
+-----------------------------------+-----------------------------------+
When a Ref<'_, T> or RefMut<'_, T> guard goes out of scope, its Drop implementation automatically decrements or resets the borrow counter.
Borrowing Methods
| Method | Return Type | Behavior on Conflict |
|---|---|---|
.borrow() |
Ref<'_, T> (derefs to &T) |
Panics if a RefMut is currently active |
.borrow_mut() |
RefMut<'_, T> (derefs to &mut T) |
Panics if any Ref or RefMut is currently active |
.try_borrow() |
Result<Ref<'_, T>, BorrowError> |
Returns Err(BorrowError) instead of panicking |
.try_borrow_mut() |
Result<RefMut<'_, T>, BorrowMutError> |
Returns Err(BorrowMutError) instead of panicking |
use std::cell::RefCell;
fn main() {
let messages = RefCell::new(vec![String::from("boot")]);
{
let mut writer = messages.borrow_mut();
writer.push(String::from("ready"));
} // `writer` guard is dropped here, releasing the mutable borrow.
let reader1 = messages.borrow();
let reader2 = messages.borrow();
println!("reader1: {:?}, reader2 len: {}", *reader1, reader2.len());
println!("try_borrow_mut while shared: {:?}", messages.try_borrow_mut().is_ok());
}
reader1: ["boot", "ready"], reader2 len: 2
try_borrow_mut while shared: false
Runtime Borrow Panic
If .borrow_mut() is called while a Ref<'_, T> or another RefMut<'_, T> is still alive in the same scope, the program panics immediately to prevent aliased mutable references:
use std::cell::RefCell;
fn main() {
let cell = RefCell::new(100);
let shared_guard = cell.borrow();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let _exclusive_guard = cell.borrow_mut();
}));
println!("kept shared_guard = {}", *shared_guard);
println!("borrow_mut panicked: {}", result.is_err());
}
kept shared_guard = 100
borrow_mut panicked: true
Projecting Guards with Ref::map and RefMut::map_split
Ref::map and RefMut::map transform a guard over a whole struct into a guard over a specific subfield (Ref<'_, Field>) while preserving the underlying borrow count. RefMut::map_split splits a single RefMut into two independent mutable guards over disjoint parts of a struct or slice:
use std::cell::{Ref, RefCell, RefMut};
struct ServerState {
active_users: Vec<String>,
error_count: u64,
}
fn main() {
let state = RefCell::new(ServerState {
active_users: vec![String::from("alice")],
error_count: 0,
});
{
let (mut users, mut errors): (RefMut<'_, Vec<String>>, RefMut<'_, u64>) =
RefMut::map_split(state.borrow_mut(), |s| (&mut s.active_users, &mut s.error_count));
users.push(String::from("bob"));
*errors += 1;
}
let first_user: Ref<'_, str> = Ref::map(state.borrow(), |s| s.active_users[0].as_str());
println!("first_user: {}, errors: {}", &*first_user, state.borrow().error_count);
}
first_user: alice, errors: 1