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: []