Smart Pointers

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 shared Ref<'_, T> guards.
  • borrow < 0 (-1 for a whole-cell mutable borrow): An active exclusive RefMut<'_, 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