In this lesson, you will learn how to implement your own interior mutability primitive by leveraging UnsafeCell to bypass Rust’s strict aliasing rules.
The Foundation of Mutability
Usually, Rust enforces the "aliasing XOR mutability" rule: you can have many immutable references, or exactly one mutable reference, but never both. However, types like Cell and RefCell work because they use UnsafeCell<T> internally. UnsafeCell is the only way to perform "interior mutability" in Rust; it tells the compiler, "The data inside here may change even if the container itself is held behind an immutable reference."
Building a Simple Wrapper
To see how this works, let's build a minimal version of Cell. We want a struct that allows us to update its value even when we only have a shared reference to the struct.
use std::cell::UnsafeCell;
pub struct SimpleCell<T> {
value: UnsafeCell<T>,
}
impl<T> SimpleCell<T> {
pub fn new(value: T) -> Self {
Self { value: UnsafeCell::new(value) }
}
pub fn set(&self, val: T) {
// SAFETY: We are writing to the interior, but we must ensure
// there are no active references to the data being overwritten.
unsafe { *self.value.get() = val; }
}
pub fn get(&self) -> T where T: Copy {
// SAFETY: We are reading from the interior. Because T is Copy,
// we return a snapshot, maintaining safety.
unsafe { *self.value.get() }
}
}
Why this is "Unsafe"
The *self.value.get() call returns a raw pointer (*mut T). When you dereference this pointer to perform an assignment, you are effectively telling the compiler to ignore its safety checks.
This is powerful but dangerous. If you were to implement a RefCell-like structure, you would need to add manual runtime tracking (like a borrow counter) to ensure that get() isn't used to create a mutable reference while other references exist. UnsafeCell provides the raw mechanism, but it puts the entire burden of proof—ensuring you aren't creating undefined behavior—squarely on your shoulders as the library author.
Key Takeaway: UnsafeCell is the primitive block that enables interior mutability, but it requires you to manually uphold safety invariants that the compiler would normally enforce for you.