Code Example Template
let cell = RefCell::new(5);
*cell.borrow_mut() += 1;

Deep-Dive System Documentation

Interior Mutability: Bypassing Compile-Time Borrowing Rules

Rust's borrow checker enforces a strict rule: you can have either one mutable reference (&mut T) OR any number of immutable references (&T) to a value, but never both at the same time. Interior mutability is a design pattern that lets you mutate a value inside an immutable container, shifting borrow checking from compile-time to runtime.

Cell<T> vs RefCell<T>

FeatureCell<T>RefCell<T>
Mutation StyleReplaces the value completely (no references)Borrow references dynamically at runtime
OverheadZero runtime overhead (compiler optimized copy)Tiny overhead (keeps track of borrows dynamically)
RestrictionsRequires Copy or taking ownership of valueWorks with any non-Copy or complex type

Using Cell<T> (Zero Overhead)

Cell works by moving values in and out of the container, rather than giving you direct references.

use std::cell::Cell;

let count = Cell::new(42);
count.set(50); // Mutates inside an immutable container
println!("Value: {}", count.get()); // 50

Using RefCell<T> (Runtime Borrow Checking)

RefCell tracks borrow flags at runtime. You get dynamically checked references using .borrow() and .borrow_mut().

use std::cell::RefCell;

let shared = RefCell::new(String::from("Rust"));

{
    // Shared immutable borrows
    let b1 = shared.borrow();
    let b2 = shared.borrow();
    println!("Borrows: {} & {}", b1, b2);
} // b1 and b2 dropped here

{
    // Mutable borrow
    let mut b_mut = shared.borrow_mut();
    b_mut.push_str(" Cheat Sheet");
} // b_mut dropped here

Runtime Panic (The Danger)

If you violate borrowing rules with RefCell at runtime, the code compiles successfully, but panics at runtime:

let data = RefCell::new(5);
let b1 = data.borrow();
let mut b2 = data.borrow_mut(); // 💥 PANIC: already borrowed!
  • Safe Check: Use .try_borrow() and .try_borrow_mut() to return a Result instead of panicking.

Thread Safety

Neither Cell nor RefCell are thread-safe. They do not implement Sync, preventing data races from happening across threads. For thread-safe interior mutability, use Mutex<T> or RwLock<T>.


Useful Methods: Cell

Cell::new(value: T) -> Cell<T>

Creates a new Cell containing the given value.

use std::cell::Cell;
let my_cell = Cell::new(42);

.get(&self) -> T

Returns a copy of the contained value. Only available if T implements the Copy trait.

use std::cell::Cell;
let my_cell = Cell::new(10);
assert_eq!(my_cell.get(), 10);

Useful Methods: RefCell

.borrow(&self) -> Ref<'_, T>

Immutably borrows the wrapped value. Multiple immutable borrows can be active at the same time. Panics if the value is currently mutably borrowed.

use std::cell::RefCell;
let c = RefCell::new(5);
let r1 = c.borrow();
let r2 = c.borrow();
assert_eq!(*r1, 5);

.borrow_mut(&self) -> RefMut<'_, T>

Mutably borrows the wrapped value. Only one mutable borrow can be active, and no other borrows may exist. Panics if the value is currently borrowed.

use std::cell::RefCell;
let c = RefCell::new(5);
*c.borrow_mut() += 10;
assert_eq!(*c.borrow(), 15);

Quick Reference Guide

use std::cell::{Cell, RefCell};
let c = Cell::new(10);
c.set(20);
let r = RefCell::new(String::from('A'));
let mut ref_mut = r.borrow_mut();
ref_mut.push('B');