replace() and take()
replace() and take() are value-extraction methods provided on both Cell<T> and RefCell<T> that move the current value out of the cell through a shared reference &self by simultaneously putting a replacement value (val or T::default()) in its place.
How replace() and take() Work on Non-Copy Types
Because Cell::get() requires T: Copy, extracting a non-Copy value such as String, Vec<T>, or Box<T> from a Cell<T> requires leaving another valid T behind so the Cell<T> remains initialized:
cell.replace(new_val):
+--------------------+
new_val -------> | Cell<T> / RefCell | -------> returns old_val (moved out!)
+--------------------+
cell.take() (where T: Default):
+--------------------+
T::default() --> | Cell<T> / RefCell | -------> returns old_val (moved out!)
+--------------------+
| Method | Available On | Trait Bound on T |
Equivalent Expression |
|---|---|---|---|
.replace(val) |
Cell<T>, RefCell<T> |
Any T (Sized) |
Swaps val into the cell and returns the old T |
.take() |
Cell<T>, RefCell<T> |
T: Default |
self.replace(T::default()) |
.swap(&other) |
Cell<T>, RefCell<T> |
Any T (Sized) |
Exchanges values between two cells without Default |
Mutating a Non-Copy Cell<T> via the Take-Modify-Set Idiom
When a struct stores a Vec<T> or String inside a Cell (avoiding the 8-byte borrow counter and panic branch of RefCell), a method can call .take() to move the collection out, mutate it as an owned local variable (where Vec::new() or String::new() costs zero heap allocations as the temporary placeholder), and put it back with .set():
use std::cell::Cell;
struct EventCollector {
events: Cell<Vec<String>>,
}
impl EventCollector {
fn new() -> Self {
Self {
events: Cell::new(Vec::new()),
}
}
fn record(&self, event: &str) {
// 1. Move the Vec out, leaving an empty zero-allocation Vec::new() in the Cell:
let mut list = self.events.take();
// 2. Mutate the owned Vec directly:
list.push(event.to_string());
// 3. Move the updated Vec back into the Cell:
self.events.set(list);
}
fn drain_all(&self) -> Vec<String> {
self.events.take()
}
}
fn main() {
let collector = EventCollector::new();
collector.record("user.login");
collector.record("file.upload");
let batch = collector.drain_all();
println!("drained batch: {:?}", batch);
println!("after drain: {:?}", collector.drain_all());
}
drained batch: ["user.login", "file.upload"]
after drain: []
Extracting One-Shot Values with Cell<Option<T>>
When a type T does not implement Default (such as a JoinHandle<()> or a one-time callback Box<dyn FnOnce()>), wrapping it in Option<T> satisfies Option<T>: Default (whose default is None). Calling .take() on Cell<Option<T>> or RefCell<Option<T>> moves Some(T) out and leaves None in its place:
use std::cell::Cell;
struct OneShotTask {
callback: Cell<Option<Box<dyn FnOnce()>>>,
}
impl OneShotTask {
fn new(f: impl FnOnce() + 'static) -> Self {
Self {
callback: Cell::new(Some(Box::new(f))),
}
}
fn run_once(&self) {
if let Some(task) = self.callback.take() {
task();
} else {
println!("task was already executed");
}
}
}
fn main() {
let job = OneShotTask::new(|| println!("executing one-shot initialization!"));
job.run_once();
job.run_once();
}
executing one-shot initialization!
task was already executed