Skip to content
Cell, RefCell, and Interior Mutability
step 1/4

Reading — step 1 of 4

Learn

~1 min readSmart Pointers and Interior Mutability

Rust's borrow rules are checked at COMPILE time. Interior mutability types defer the check to RUNTIME — letting you mutate through a shared reference.

Cell<T> — for Copy types only. Get/set via copy:

use std::cell::Cell;

struct Counter {
    count: Cell<i32>,             // mutable through &self
}

impl Counter {
    fn increment(&self) {         // note: &self, not &mut self
        self.count.set(self.count.get() + 1);
    }
}

The outer borrow can be &self (immutable) but count is still mutable inside.

RefCell<T> — for non-Copy types. Borrow at runtime:

use std::cell::RefCell;

struct Cache {
    items: RefCell<Vec<String>>,
}

impl Cache {
    fn add(&self, item: String) {
        self.items.borrow_mut().push(item);     // panics if already borrowed
    }
    fn get(&self, idx: usize) -> Option<String> {
        self.items.borrow().get(idx).cloned()
    }
}

borrow() and borrow_mut() are runtime checks. Multiple shared borrows OK; multiple mutable, or shared+mutable, panics.

Common pattern: Rc + RefCell = shared mutable state (single thread):

let shared = Rc::new(RefCell::new(vec![1, 2, 3]));

let copy = Rc::clone(&shared);
copy.borrow_mut().push(4);
println!("{:?}", shared.borrow());     // [1, 2, 3, 4]

For threads, use Arc + Mutex (or RwLock) — same idea, thread-safe.

When to use:

  • Most code: don't need interior mutability — borrow checker is sufficient
  • When you need to mutate inside an immutable method (cache, counter)
  • When you have a graph or doubly-linked structure (Rc/RefCell)
  • When you have GUI/observer patterns

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…