:#/ GSLF Journal
← :#/ GSLF Journal

Rustlers Atom 2.9: RefCell/Cell interior mutability

On this page

<figure class="video" style="margin:0;">
<div style="position:relative;width:100%;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube.com/embed/sXePxN4BdRE?si=eRM_E0c8366PgrWr"
title="YouTube video player"
style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
</figure>

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.