In this lesson, you will learn how to safely bypass Rust’s strict borrowing rules to achieve "internal mutability" using the UnsafeCell primitive.
The Rule-Breaker
Normally, Rust’s borrow checker is absolute: you can have either one mutable reference (&mut T) or multiple immutable references (&T), but never both. This prevents data races and ensures memory safety. However, when building low-level abstractions—like a Cell or a Mutex—you occasionally need to mutate data even when you only hold an immutable reference to the container.
This is where UnsafeCell<T> comes in. It is the only legal way in Rust to obtain a mutable pointer (*mut T) from an immutable reference (&T).
How It Works
Think of UnsafeCell<T> as a "blind spot" for the borrow checker. By wrapping your data in an UnsafeCell, you are telling the compiler: "I take full responsibility for the safety of this data; please stop enforcing the borrow rules on this specific field."
Here is a simplified look at how you might use it:
use std::cell::UnsafeCell;
struct SimpleCell<T> {
value: UnsafeCell<T>,
}
impl<T> SimpleCell<T> {
fn new(value: T) -> Self {
SimpleCell { value: UnsafeCell::new(value) }
}
// We can mutate the value even though we only have &self!
fn set(&self, new_value: T) {
unsafe {
*self.value.get() = new_value;
}
}
}
The Responsibility
Because UnsafeCell bypasses the compiler's safety guarantees, the "unsafe" burden shifts entirely to you. If you allow multiple parts of your code to mutate the interior of an UnsafeCell simultaneously, you will cause Undefined Behavior.
You must ensure that your higher-level logic enforces the borrowing rules manually. For instance, a RefCell uses UnsafeCell internally but tracks the number of active references at runtime to panic if you try to mutate while shared references exist. UnsafeCell is the foundation upon which all safe internal mutability is built.
Key Takeaway: UnsafeCell is the primitive that allows you to perform interior mutability by opting out of the borrow checker's static analysis, making you solely responsible for ensuring no data races occur.