RefCell<T> es un tipo que comprueba los préstamos al ejecutar el programa.
El Borrow Checker de Rust es como un policía de tráfico muy estricto. Antes de dejarte arrancar el coche (compilar), revisa que todas las referencias sean seguras.
Sin embargo, a veces este policía es demasiado conservador y rechaza programas que son lógicamente correctos pero difíciles de analizar estáticamente.
Para estos casos, Rust nos ofrece RefCell<T>.
Con RefCell, el policía de tráfico no está en la entrada de la autopista (compilación), sino que se sube contigo al coche (tiempo de ejecución). Si intentas romper las reglas de préstamo mientras conduces, el programa no fallará al compilar, pero entrará en Pánico (panic!) y se estrellará mientras corre.
El patrón de mutabilidad interior
La Mutabilidad Interior (Interior Mutability) es un patrón de diseño en Rust que permite mutar datos dentro de una estructura inmutable.
El RefCell<T> envuelve tu dato y te permite pedir prestadas referencias mutables (&mut T) incluso si el RefCell en sí mismo es inmutable (&RefCell<T>).
Cómo funciona
RefCell tiene dos métodos principales para acceder a los datos:
.borrow(): Te da una referencia de lectura (Ref<T>). Puedes pedir muchas..borrow_mut(): Te da una referencia de escritura (RefMut<T>). Solo puedes pedir una.
use std::cell::RefCell;
fn main() {
// Fíjate que 'caja' NO es 'mut'
let caja = RefCell::new(5);
// Obtenemos acceso mutable mediante las comprobaciones de RefCell
*caja.borrow_mut() += 10;
println!("Valor: {:?}", caja.borrow()); // 15
}Si intentáramos hacer esto con un Box o una variable normal sin mut, el compilador lo rechazaría. Con RefCell, la validez de cada préstamo se comprueba cuando llamamos a estos métodos.
El riesgo: pánicos en tiempo de ejecución
RefCell lleva la cuenta de cuántos lectores y escritores hay activos mientras el programa corre.
Si intentas violar las reglas (por ejemplo, tener un borrow y un borrow_mut activos a la vez), el programa explotará.
let caja = RefCell::new(5);
let escribir = caja.borrow_mut(); // Tenemos el préstamo mutable
let leer = caja.borrow(); // 💥 ¡PÁNICO! Ya hay un préstamo mutable activo.
// El programa termina con:
// thread 'main' panicked at 'already borrowed: BorrowMutError'Es tu responsabilidad como programador asegurarte de que esto no ocurra.
Si el conflicto puede ser una situación esperada, try_borrow() y try_borrow_mut() devuelven un Result en lugar de provocar un pánico.
Combinar Rc<RefCell<T>>
En el artículo anterior vimos que Rc<T> permite tener múltiples dueños, pero solo de lectura.
Ahora hemos visto que RefCell<T> permite mutabilidad interior, pero por sí solo no comparte la propiedad.
¿Qué pasa si los combinamos? Obtenemos lo mejor de los dos mundos: Múltiples dueños que pueden modificar el dato.
Esta combinación aparece en estructuras como grafos, árboles con enlaces compartidos o ciertos patrones de observador.
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
// 1. Creamos el dato: Un entero (5) dentro de RefCell (mutable),
// dentro de Rc (compartido).
let valor = Rc::new(RefCell::new(5));
// 2. Clonamos el Rc. Ahora tenemos 3 dueños del mismo dato.
let a = Rc::clone(&valor);
let b = Rc::clone(&valor);
let c = Rc::clone(&valor);
// 3. Modificamos el dato a través del dueño 'a'
*a.borrow_mut() += 10;
// 4. Leemos el dato a través del dueño 'b'
println!("b ve el valor: {}", b.borrow()); // 15
println!("c ve el valor: {}", c.borrow()); // 15
}Desglosar las capas
Rccontrola la vida del objeto (Reference Counting).RefCellcontrola el acceso (Borrow Rules dinámicas).i32es el dato real.
¿Cuándo usar RefCell?
No lo uses por defecto. Es apropiado cuando el modelo necesita mutabilidad interior y podemos controlar el alcance de los préstamos dinámicos.
- Objetos Mock en Tests: Quieres testear una función que modifica algo interno, pero la firma del Trait dice que el argumento es
&self(inmutable). - Caché / Memoización: Quieres guardar un resultado costoso dentro de un struct la primera vez que se calcula. Desde fuera, el método parece de solo lectura (
get_valor), pero internamente necesita escribir en el campocache. - Estructuras de datos complejas: Grafos donde los nodos se apuntan entre sí y cambian.
RefCell vs Cell
Existe una versión más ligera llamada Cell<T>.
Cell<T>: No entrega referencias al interior. Su métodoget()requiereT: Copy, pero también puede reemplazar o extraer valores noCopymediante otros métodos. Al no mantener préstamos dinámicos, estas operaciones no provocan conflictos de préstamo.RefCell<T>: Funciona para todo, usa referencias y puede entrar en pánico.