Cell y RefCell

Cell y RefCell implementan lo que Rust llama mutabilidad interna: mutación de valores en un contexto inmutable.

Normalmente, Cell se utiliza para tipos simples, ya que requiere copiar o mover valores. Los tipos internos más complejos normalmente utilizan RefCell, que realiza un seguimiento de las referencias compartidas y exclusivas en tiempo de ejecución y entra en pánico si se utilizan de forma incorrecta.

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug, Default)]
struct Node {
    value: i64,
    children: Vec<Rc<RefCell<Node>>>,
}

impl Node {
    fn new(value: i64) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Node { value, ..Node::default() }))
    }

    fn sum(&self) -> i64 {
        self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>()
    }
}

fn main() {
    let root = Node::new(1);
    root.borrow_mut().children.push(Node::new(5));
    let subtree = Node::new(10);
    subtree.borrow_mut().children.push(Node::new(11));
    subtree.borrow_mut().children.push(Node::new(12));
    root.borrow_mut().children.push(subtree);

    println!("graph: {root:#?}");
    println!("graph sum: {}", root.borrow().sum());
}
  • Si estuviéramos utilizando Cell en lugar de RefCell en este ejemplo, tendríamos que mover el Node fuera del Rc para insertar hijos y luego volver a moverlo. Esto es seguro porque siempre hay un valor sin referenciar en la celda, pero no es ergonómico.
  • Para hacer cualquier cosa con un Node, debes llamar a un método de RefCell, normalmente borrow o borrow_mut.
  • Demuestra que se pueden crear bucles de referencia añadiendo root a subtree.children (¡no intentes imprimirlo!).
  • Para demostrar un pánico en tiempo de ejecución, añade un fn inc(&mut self) que incremente self.value y llame al mismo método en sus hijos. Esto entrará en pánico en presencia del bucle de referencia, con thread 'main' panicked at 'already borrowed: BorrowMutError'.