Loading page…
Rust walkthroughs
Loading page…
Cell provides interior mutability for Copy types. It allows you to mutate the inner value through a shared reference (&T), bypassing Rust's normal borrowing rules. This is achieved by moving values in and out rather than through direct mutation.
Key concepts:
Cell<T> requires T: Copy&T or &mut T to the inner valueCell is not thread-safe (!Sync)When to use Cell:
Copy types through shared referencesWhen NOT to use Cell:
Copy types (use RefCell instead)AtomicXxx or Mutex)use std::cell::Cell;
fn main() {
let cell = Cell::new(42);
// Get a copy of the value
let value = cell.get();
println!("Initial value: {}", value);
// Set a new value (through shared reference!)
cell.set(100);
println!("New value: {}", cell.get());
}use std::cell::Cell;
struct Counter {
count: Cell<u32>,
}
impl Counter {
fn new() -> Self {
Self {
count: Cell::new(0),
}
}
fn increment(&self) {
// Mutate through &self!
let current = self.count.get();
self.count.set(current + 1);
}
fn get(&self) -> u32 {
self.count.get()
}
}
fn main() {
let counter = Counter::new();
// Multiple shared references can mutate
let ref1 = &counter;
let ref2 = &counter;
ref1.increment();
ref2.increment();
counter.increment();
println!("Count: {}", counter.get());
}use std::cell::Cell;
struct CachedValue<T> {
value: Cell<Option<T>>,
computed: Cell<bool>,
}
impl<T: Copy> CachedValue<T> {
fn new() -> Self {
Self {
value: Cell::new(None),
computed: Cell::new(false),
}
}
fn get_or_compute<F>(&self, compute: F) -> T
where
F: FnOnce() -> T,
{
if !self.computed.get() {
self.value.set(Some(compute()));
self.computed.set(true);
}
self.value.get().unwrap()
}
fn invalidate(&self) {
self.computed.set(false);
self.value.set(None);
}
}
fn main() {
let cache = CachedValue::new();
let value = cache.get_or_compute(|| {
println!("Computing...");
42
});
println!("Value: {}", value);
let cached = cache.get_or_compute(|| {
println!("Computing again...");
100
});
println!("Cached: {}", cached);
}use std::cell::Cell;
#[derive(Clone, Copy)]
enum State {
Idle,
Running,
Paused,
Stopped,
}
struct Machine {
state: Cell<State>,
}
impl Machine {
fn new() -> Self {
Self {
state: Cell::new(State::Idle),
}
}
fn start(&self) {
match self.state.get() {
State::Idle | State::Paused => {
self.state.set(State::Running);
println!("Started");
}
_ => println!("Cannot start from current state"),
}
}
fn pause(&self) {
if self.state.get() == State::Running {
self.state.set(State::Paused);
println!("Paused");
}
}
fn stop(&self) {
self.state.set(State::Stopped);
println!("Stopped");
}
}
fn main() {
let machine = Machine::new();
machine.start();
machine.pause();
machine.start();
machine.stop();
}use std::cell::Cell;
#[derive(Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
struct Transform {
position: Cell<Point>,
}
impl Transform {
fn new(x: i32, y: i32) -> Self {
Self {
position: Cell::new(Point { x, y }),
}
}
fn move_by(&self, dx: i32, dy: i32) {
let mut pos = self.position.get();
pos.x += dx;
pos.y += dy;
self.position.set(pos);
}
fn position(&self) -> Point {
self.position.get()
}
}
fn main() {
let transform = Transform::new(0, 0);
println!("Position: ({}, {})", transform.position().x, transform.position().y);
transform.move_by(10, 20);
println!("After move: ({}, {})", transform.position().x, transform.position().y);
}use std::cell::Cell;
struct Toggle {
state: Cell<bool>,
}
impl Toggle {
fn new(initial: bool) -> Self {
Self {
state: Cell::new(initial),
}
}
fn toggle(&self) {
self.state.set(!self.state.get());
}
fn is_on(&self) -> bool {
self.state.get()
}
}
fn main() {
let toggle = Toggle::new(false);
println!("Initial: {}", toggle.is_on());
toggle.toggle();
println!("After toggle: {}", toggle.is_on());
toggle.toggle();
println!("After another toggle: {}", toggle.is_on());
}use std::cell::Cell;
fn main() {
let cell = Cell::new(42);
// Replace the value and get the old one
let old = cell.replace(100);
println!("Old: {}, New: {}", old, cell.get());
// Swap two cells
let a = Cell::new(1);
let b = Cell::new(2);
a.swap(&b);
println!("a: {}, b: {}", a.get(), b.get());
}use std::cell::Cell;
fn main() {
let cell = Cell::new(Some(42));
// Take the value, leaving Default::default()
let value = cell.take();
println!("Took: {:?}", value);
println!("After take: {:?}", cell.get());
}use std::cell::Cell;
use std::collections::HashMap;
struct VisitedTracker {
visited: HashMap<String, Cell<bool>>,
}
impl VisitedTracker {
fn new() -> Self {
Self {
visited: HashMap::new(),
}
}
fn mark_visited(&self, key: &str) {
self.visited
.entry(key.to_string())
.or_insert(Cell::new(false))
.set(true);
}
fn is_visited(&self, key: &str) -> bool {
self.visited.get(key).map_or(false, |c| c.get())
}
}
fn main() {
let tracker = VisitedTracker::new();
tracker.mark_visited("page1");
tracker.mark_visited("page2");
println!("page1 visited: {}", tracker.is_visited("page1"));
println!("page3 visited: {}", tracker.is_visited("page3"));
}use std::cell::Cell;
use std::ptr::NonNull;
struct SimpleRcInner<T> {
value: T,
ref_count: Cell<usize>,
}
pub struct SimpleRc<T> {
inner: NonNull<SimpleRcInner<T>>,
}
impl<T> SimpleRc<T> {
pub fn new(value: T) -> Self {
let inner = Box::new(SimpleRcInner {
value,
ref_count: Cell::new(1),
});
Self {
inner: unsafe { NonNull::new_unchecked(Box::into_raw(inner)) },
}
}
pub fn get(&self) -> &T {
unsafe { &self.inner.as_ref().value }
}
fn ref_count(&self) -> usize {
unsafe { self.inner.as_ref().ref_count.get() }
}
}
impl<T> Clone for SimpleRc<T> {
fn clone(&self) -> Self {
unsafe {
self.inner.as_ref().ref_count.set(
self.inner.as_ref().ref_count.get() + 1
);
}
Self { inner: self.inner }
}
}
impl<T> Drop for SimpleRc<T> {
fn drop(&mut self) {
unsafe {
let inner = self.inner.as_ref();
let count = inner.ref_count.get();
if count == 1 {
drop(Box::from_raw(self.inner.as_ptr()));
} else {
inner.ref_count.set(count - 1);
}
}
}
}
fn main() {
let rc1 = SimpleRc::new(42);
println!("Value: {}, refs: {}", rc1.get(), rc1.ref_count());
let rc2 = rc1.clone();
println!("Value: {}, refs: {}", rc1.get(), rc1.ref_count());
drop(rc2);
println!("After drop, refs: {}", rc1.ref_count());
}use std::cell::{Cell, RefCell};
fn main() {
// Cell: Only for Copy types
let cell: Cell<i32> = Cell::new(42);
cell.set(100);
let value = cell.get();
// RefCell: For any type, but with runtime borrow checking
let refcell: RefCell<String> = RefCell::new(String::from("hello"));
refcell.borrow_mut().push_str(" world");
// Cell cannot hold non-Copy types:
// let bad: Cell<String> = Cell::new(String::from("test")); // Error!
// RefCell can hold any type:
let good: RefCell<Vec<i32>> = RefCell::new(vec![1, 2, 3]);
good.borrow_mut().push(4);
println!("Cell: {}", value);
println!("RefCell: {}", refcell.borrow());
println!("RefCell Vec: {:?}", good.borrow());
}use std::cell::Cell;
pub struct Config {
debug: Cell<bool>,
log_level: Cell<u32>,
}
impl Config {
pub fn new() -> Self {
Self {
debug: Cell::new(false),
log_level: Cell::new(1),
}
}
// Immutable API that internally mutates
pub fn enable_debug(&self) {
self.debug.set(true);
}
pub fn set_log_level(&self, level: u32) {
self.log_level.set(level);
}
pub fn is_debug(&self) -> bool {
self.debug.get()
}
pub fn log_level(&self) -> u32 {
self.log_level.get()
}
}
fn main() {
let config = Config::new();
// Can modify through shared reference
let config_ref = &config;
config_ref.enable_debug();
config_ref.set_log_level(3);
println!("Debug: {}, Level: {}", config.is_debug(), config.log_level());
}use std::cell::Cell;
#[derive(Clone, Copy, PartialEq)]
enum ParserState {
Start,
InWord,
InNumber,
End,
}
struct Parser {
state: Cell<ParserState>,
position: Cell<usize>,
}
impl Parser {
fn new() -> Self {
Self {
state: Cell::new(ParserState::Start),
position: Cell::new(0),
}
}
fn advance(&self, input: &[char]) -> Option<char> {
let pos = self.position.get();
if pos < input.len() {
let ch = input[pos];
self.position.set(pos + 1);
self.update_state(ch);
Some(ch)
} else {
self.state.set(ParserState::End);
None
}
}
fn update_state(&self, ch: char) {
let new_state = match (self.state.get(), ch) {
(ParserState::Start, c) if c.is_alphabetic() => ParserState::InWord,
(ParserState::Start, c) if c.is_numeric() => ParserState::InNumber,
(ParserState::InWord, c) if c.is_alphabetic() => ParserState::InWord,
(ParserState::InNumber, c) if c.is_numeric() => ParserState::InNumber,
_ => ParserState::Start,
};
self.state.set(new_state);
}
fn state(&self) -> ParserState {
self.state.get()
}
}
fn main() {
let parser = Parser::new();
let input = ['h', 'e', 'l', 'l', 'o', ' ', '1', '2', '3'];
while let Some(ch) = parser.advance(&input) {
println!("Char: {}, State: {:?}", ch, parser.state());
}
}use std::cell::Cell;
fn main() {
let cell = Cell::new(0u32);
// Cell operations are very cheap - just memory reads/writes
// No runtime borrow checking overhead
for i in 0..1000 {
cell.set(i);
let _ = cell.get();
}
println!("Final value: {}", cell.get());
// Cell<T> has the same size as T
println!("Size of Cell<u32>: {}", std::mem::size_of::<Cell<u32>>());
println!("Size of u32: {}", std::mem::size_of::<u32>());
}Cell Methods:
| Method | Description |
|--------|-------------|
| new(value) | Create a new Cell |
| get() | Get a copy of the value |
| set(value) | Set a new value |
| replace(value) | Replace and return old value |
| swap(&other) | Swap with another Cell |
| take() | Replace with default and return old value |
| into_inner() | Consume and return inner value |
| as_ptr() | Get raw pointer to inner value |
Cell vs Other Interior Mutability Types:
| Type | Works With | Thread Safe | Overhead |
|------|------------|-------------|----------|
| Cell<T> | Copy types only | No (!Sync) | None |
| RefCell<T> | Any type | No (!Sync) | Runtime borrow check |
| Mutex<T> | Any type | Yes | Lock overhead |
| AtomicXxx | Specific numeric types | Yes | Atomic ops |
When to Use Cell:
| Scenario | Appropriate? |
|----------|-------------|
| Small Copy types | ✅ Yes |
| Counters, flags | ✅ Yes |
| State machines | ✅ Yes |
| Caching | ✅ Yes |
| Non-Copy types | ❌ Use RefCell |
| Thread-safe mutation | ❌ Use Mutex or atomics |
| Need &T or &mut T | ❌ Use RefCell |
Key Points:
Cell<T> provides interior mutability for Copy types!Sync)T)RefCell for non-Copy types or when you need referencesMutex or atomics for thread-safe mutation