Interior Mutability
RefCell<T>
std::cell::RefCell<T> is a single-threaded container that provides interior mutability with dynamically checked borrowing. Unlike Cell<T>, which only moves or copies whole values, RefCell<T> lends out smart-pointer guards (Ref<'_, T> and RefMut<'_, T>) that dereference directly to &T and &mut T.
Internal Structure of RefCell<T>
RefCell<T> pairs an UnsafeCell<T> with a Cell<BorrowFlag> (isize) that tracks active borrows at runtime:
RefCell<T> Layout (size_of::<T>() + 8 bytes on 64-bit targets):
+-------------------------------+---------------------------------------+
| borrow: Cell<isize> | value: UnsafeCell<T> |
| 0 = unborrowed | (in-place storage of T, no heap |
| 1..MAX = reader count | allocation by RefCell itself) |
| -1 = exclusive writer | |
+-------------------------------+---------------------------------------+
use std::cell::RefCell;
fn main() {
println!("size of String: {} bytes", size_of::<String>());
println!("size of RefCell<String>: {} bytes", size_of::<RefCell<String>>());
let buffer = RefCell::new(String::from("hello"));
buffer.borrow_mut().push_str(", world");
println!("buffer: {:?}", buffer.borrow());
}
size of String: 24 bytes
size of RefCell<String>: 32 bytes
buffer: "hello, world"
Overview of RefCell<T> Methods
| Method | Receiver | Return Type | Runtime Borrow Check |
|---|---|---|---|
RefCell::new(val) |
— | RefCell<T> |
None (const fn) |
.borrow() |
&self |
Ref<'_, T> |
Increments reader count; panics if writer active |
.borrow_mut() |
&self |
RefMut<'_, T> |
Sets writer flag; panics if any reader or writer active |
.try_borrow() |
&self |
Result<Ref<'_, T>, BorrowError> |
Non-panicking shared borrow |
.try_borrow_mut() |
&self |
Result<RefMut<'_, T>, BorrowMutError> |
Non-panicking exclusive borrow |
.replace(val) |
&self |
T |
Calls borrow_mut() internally and swaps val with old T |
.take() |
&self (T: Default) |
T |
Calls replace(T::default()) |
.swap(&other) |
&self |
() |
Borrows both cells mutably and swaps their values |
.get_mut() |
&mut self |
&mut T |
Zero runtime check (compile-time &mut guarantees uniqueness) |
.into_inner() |
self |
T |
Zero runtime check (consumes the RefCell) |
Convenience Value Replacement: .replace(), .replace_with(), and .take()
When you only need to swap a value in or out of a RefCell<T> without keeping a RefMut guard alive across statements, .replace(), .replace_with(), and .take() acquire and immediately drop the mutable borrow inside a single call:
use std::cell::RefCell;
fn main() {
let history = RefCell::new(vec![10, 20]);
let old = history.replace(vec![100]);
println!("old vector: {:?}", old);
history.replace_with(|prev| {
prev.push(200);
std::mem::take(prev)
});
println!("after replace_with: {:?}", history.borrow());
let taken = history.take();
println!("taken: {:?}, remaining: {:?}", taken, history.borrow());
}
old vector: [10, 20]
after replace_with: [100, 200]
taken: [100, 200], remaining: []