Rustlers Atom 2.9: RefCell/Cell interior mutability

In rust you cannot mutate data behind a shared (immutable) reference. But what if you need to? What if you have a struct that is logically immutable to the outside world, but needs to update an internal counter, a cache, or a log?

This pattern is called Interior Mutability. Rust provides safe wrappers that let you bend the rules by moving the checks from compile time to runtime.

Cell<T>: Copying in and out

Cell is for types that implement Copy (like integers or booleans). It doesn’t give you a reference to the inner data. Instead, it lets you copy the value out or swap a new value in, even via an immutable reference.

use std::cell::Cell;

struct Sensor{
    id: String,
    counter: Cell<u8>,
}

impl Sensor{
    fn increment(s:Self){
        let mut current = s.counter.get();
        current += 1;
        s.counter.set(current);
    }
}

fn main() {
    let s = Sensor{
        id: String::from("SENSOR"),
        counter: Cell::new(0),
    };

    s.increment();
    println!("{} counter is {}", s.id, s.counter.get());
}

Cell is safe because it never hands out pointers to its insides; it just moves values in and out.

RefCell<T>: Dynamic Borrowing

If you need references to non-Copy data (like a String or Vec), use RefCell, a struct that tracks borrows at runtime. It has an internal counter:

  • borrow(): Increments the reader count. Returns a Ref<T>.
  • borrow_mut(): Checks that reader count is 0 and writer count is 0. Returns a RefMut<T>.

If you violate the borrowing rules (e.g., call borrow_mut() while a borrow is active), the program will panic and crash.

use std::cell::RefCell;

struct SuperSensor{
    id: String,
    values: RefCell<Vec<i32>>,
}

impl SuperSensor{
    fn read(& self){
        let mut v = self.values.borrow_mut();
        v.push(1);
    }
}

fn main() {
    let s = SuperSensor{
        id: String::from("SENSOR"),
        values: RefCell::new(Vec::new()),
    };

    s.read();
    s.read();
    s.read();
    println!("{} counter is {:?}", s.id, s.values.borrow());
}

Use RefCell sparingly. It imposes a small runtime performance penalty and moves safety checks to runtime, where failures are crashes rather than compiler errors.


You'll only receive email when they publish something new.

More from GSLF
All posts