In this lesson, you will learn how to leverage UnsafeCell<T> as the fundamental building block for constructing your own thread-safe synchronization primitives from scratch.
The Problem with Shared Mutability
In Rust, the compiler enforces the rule: aliasing XOR mutation. If you have a shared reference &T, you cannot modify the data. This is great for safety, but it prevents you from implementing primitives like a Mutex or a Spinlock. These structures need to hold shared references to data that they internally mutate based on whether a lock is currently acquired.
UnsafeCell<T> is the only type in Rust that allows "interior mutability." By wrapping data in an UnsafeCell, you opt out of the compiler's strict alias-mutation rules. However, you assume the responsibility for maintaining memory safety.
Building a Simple Spinlock
To build a custom synchronization primitive, you pair UnsafeCell with an atomic flag. The flag manages the "locked/unlocked" state, while the UnsafeCell provides the raw, mutable access to the protected data.
use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicBool, Ordering};
pub struct SimpleLock<T> {
locked: AtomicBool,
data: UnsafeCell<T>,
}
// We must manually implement Sync to allow sharing across threads
unsafe impl<T: Send> Sync for SimpleLock<T> {}
impl<T> SimpleLock<T> {
pub fn new(val: T) -> Self {
Self {
locked: AtomicBool::new(false),
data: UnsafeCell::new(val),
}
}
pub fn lock(&self) -> &mut T {
// Spin until we successfully swap 'false' with 'true'
while self.locked.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed).is_err() {}
// Safety: We now own the lock, so we can return a mutable reference
unsafe { &mut *self.data.get() }
}
}
In this example, self.data.get() returns a raw pointer (*mut T). Because we gate this access behind the AtomicBool logic, we ensure that only one thread can ever obtain that &mut T at a time. The UnsafeCell effectively hides the mutation from the borrow checker, allowing us to build the logic that keeps the program sound.
Key Takeaway: UnsafeCell is the "escape hatch" that allows you to safely provide shared mutability, provided you implement external synchronization logic to prevent data races.