Las reglas del borrow checker de Rust se verifican en tiempo de compilación. Eso es bueno porque detecta errores antes de ejecutar el programa, pero a veces es demasiado conservador: hay patrones donde sabes que el acceso es correcto en runtime pero el compilador no puede comprobarlo estáticamente. Para esos casos existe RefCell<T>: mutabilidad interior con verificación en tiempo de ejecución.
Qué es la mutabilidad interior
Normalmente, para modificar un valor necesitas que la variable sea mut y que nadie más lo esté prestando. Con RefCell<T>, puedes modificar el valor a través de una referencia inmutable. Las reglas del borrow checker se siguen cumpliendo, pero la verificación ocurre en runtime, no en compilación.
borrow() y borrow_mut()
use std::cell::RefCell;
fn main() {
let dato = RefCell::new(5);
// Leer: equivalente a &dato
let r1 = dato.borrow();
let r2 = dato.borrow();
println!("{} {}", r1, r2); // OK: dos préstamos inmutables
drop(r1);
drop(r2);
// Modificar: equivalente a &mut dato
let mut r3 = dato.borrow_mut();
*r3 += 10;
println!("{}", r3); // 15
}
Panic en runtime si se viola la regla
use std::cell::RefCell;
fn main() {
let dato = RefCell::new(5);
let r1 = dato.borrow(); // OK
let r2 = dato.borrow_mut(); // PANIC en runtime: ya hay un borrow activo
println!("{} {}", r1, r2);
}
thread 'main' panicked at 'already borrowed: BorrowMutError'
Para evitar el panic, usa try_borrow() y try_borrow_mut() que devuelven Result en lugar de paniquear.
Caso de uso: mock objects en tests
trait Mensajero {
fn enviar(&self, mensaje: &str);
}
struct MockMensajero {
mensajes: RefCell<Vec<String>>,
}
impl Mensajero for MockMensajero {
fn enviar(&self, mensaje: &str) {
// La firma requiere &self (inmutable), pero necesitamos modificar mensajes
self.mensajes.borrow_mut().push(String::from(mensaje));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_envio() {
let mock = MockMensajero { mensajes: RefCell::new(vec![]) };
mock.enviar("hola");
mock.enviar("mundo");
assert_eq!(mock.mensajes.borrow().len(), 2);
}
}
Caso de uso: grafo con aristas compartidas
use std::cell::RefCell;
use std::rc::Rc;
type Nodo = Rc<RefCell<NodoDato>>;
struct NodoDato {
valor: i32,
vecinos: Vec<Nodo>,
}
fn añadir_arista(a: &Nodo, b: &Nodo) {
a.borrow_mut().vecinos.push(Rc::clone(b));
b.borrow_mut().vecinos.push(Rc::clone(a));
}
fn main() {
let n1 = Rc::new(RefCell::new(NodoDato { valor: 1, vecinos: vec![] }));
let n2 = Rc::new(RefCell::new(NodoDato { valor: 2, vecinos: vec![] }));
añadir_arista(&n1, &n2);
println!("n1 tiene {} vecinos", n1.borrow().vecinos.len());
}
Cuándo usar RefCell
| Situación | Herramienta |
|---|---|
| Mutabilidad estándar (verificación en compilación) | &mut T |
| Mutabilidad interior en un solo hilo | RefCell<T> |
| Mutabilidad interior entre hilos | Mutex<T> |
| Solo lecturas atómicas | Cell<T> (tipos Copy) |
Resumen
RefCell<T>mueve la verificación del borrow checker a runtime.borrow(): referencia inmutable (&T). Puede coexistir con otras.borrow_mut(): referencia mutable (&mut T). Exclusiva, panic si ya hay otra activa.try_borrow()/try_borrow_mut(): versiones que devuelvenResult.- Ideal para mock objects en tests y estructuras de datos cíclicas o grafos.
- Solo para un único hilo. Para varios hilos, usa
Mutex<T>.
El siguiente artículo cubre el patrón Rc<RefCell<T>>: múltiples propietarios con mutabilidad interior, con ejemplos de árbol con padre y grafo con aristas compartidas.
