Interior Mutability

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