Tired of writing cell.set(cell.get() + 1) every time you want to tweak a Cell value? Rust 1.88 added Cell::update — one call to read, transform, and write back.
The old way
Cell<T> gives you interior mutability for Copy types, but updating a value always felt clunky:
1
2
3
4
5
6
7
8
9
10
11
12
13
| use std::cell::Cell;
fn main() {
let counter = Cell::new(0u32);
// Read, modify, write back — three steps for one logical operation
counter.set(counter.get() + 1);
counter.set(counter.get() + 1);
counter.set(counter.get() + 1);
assert_eq!(counter.get(), 3);
println!("Counter: {}", counter.get());
}
|
You’re calling .get() and .set() in the same expression, which is repetitive and visually noisy — especially when the transformation is more complex than + 1.
Enter Cell::update
Stabilized in Rust 1.88, update takes a closure that receives the current value and returns the new one:
1
2
3
4
5
6
7
8
9
10
11
12
| use std::cell::Cell;
fn main() {
let counter = Cell::new(0u32);
counter.update(|n| n + 1);
counter.update(|n| n + 1);
counter.update(|n| n + 1);
assert_eq!(counter.get(), 3);
println!("Counter: {}", counter.get());
}
|
One call. No repetition of the cell name. The intent — “increment this value” — is immediately clear.
Beyond simple increments
update shines when the transformation is more involved:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| use std::cell::Cell;
fn main() {
let flags = Cell::new(0b0000_1010u8);
// Toggle bit 0
flags.update(|f| f ^ 0b0000_0001);
assert_eq!(flags.get(), 0b0000_1011);
// Clear the top nibble
flags.update(|f| f & 0b0000_1111);
assert_eq!(flags.get(), 0b0000_1011);
// Saturating shift left
flags.update(|f| f.saturating_mul(2));
assert_eq!(flags.get(), 22);
println!("Flags: {:#010b}", flags.get());
}
|
Compare that to flags.set(flags.get() ^ 0b0000_0001) — the update version reads like a pipeline of transformations.
A practical example: tracking state in callbacks
Cell::update is especially handy inside closures where you need shared mutable state without reaching for RefCell:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| use std::cell::Cell;
fn main() {
let total = Cell::new(0i64);
let prices = [199, 450, 85, 320, 1200];
let discounted: Vec<i64> = prices.iter().map(|&price| {
let final_price = if price > 500 { price * 9 / 10 } else { price };
total.update(|t| t + final_price);
final_price
}).collect();
assert_eq!(discounted, vec![199, 450, 85, 320, 1080]);
assert_eq!(total.get(), 2134);
println!("Prices: {:?}, Total: {}", discounted, total.get());
}
|
No RefCell, no runtime borrow checks, no panics — just a clean in-place update.
The signature
1
2
3
| impl<T: Copy> Cell<T> {
pub fn update(&self, f: impl FnOnce(T) -> T);
}
|
Note the T: Copy bound — this works because Cell copies the value out, passes it to your closure, and copies the result back in. If you need this for non-Copy types, you’ll still want RefCell.
Simple, ergonomic, and long overdue. Available since Rust 1.88.0.