Uno de los objetivos principales de Rust es evitar cambios en datos inmutables y prevenir condiciones de carrera en tiempo de compilación. En condiciones normales, Rust no permitirá cambiar datos a través de una referencia inmutable. Sin embargo, en sistemas con almacenamiento en caché, cálculos perezosos o lógica que requiere modificar el estado interno a través de una referencia, esto puede ser necesario. Para eso se creó el patrón de mutabilidad interna.
Sin la mutabilidad interna, implementar cachés, inicialización perezosa y muchas otras idiomáticas es difícil o imposible, manteniendo la propiedad y referencias seguras. Un ejemplo clásico es un caché dentro de una función que solo proporciona al mundo exterior una referencia inmutable de sí misma.
En Rust hay tipos especiales: Cell y RefCell (y sus análogos para la concurrencia), que permiten modificar el valor interno incluso a través de una referencia inmutable, controlando la seguridad en tiempo de ejecución en lugar de en tiempo de compilación.
Cell<T> — es un primitivo de copia que permite modificar o leer un valor sin usar referencias, pero solo para tipos que implementan Copy.RefCell<T> — permite obtener una referencia mutable "sobre la marcha" incluso con solo una referencia externa inmutable, pero si se intenta obtener al mismo tiempo dos referencias mutables o una mutable y varias inmutables, habrá un pánico en tiempo de ejecución.Ejemplo de código:
use std::cell::RefCell; struct Foo { cache: RefCell<Option<u32>>, } impl Foo { fn get_or_compute(&self) -> u32 { if let Some(val) = *self.cache.borrow() { return val; } let computed = 42; *self.cache.borrow_mut() = Some(computed); computed } }
Características clave:
¿Podemos usar RefCell de manera segura para estructuras multihilo?
No, RefCell no es seguro para hilos. Para trabajar en un entorno multihilo, use Mutex o RwLock.
¿Se puede devolver una referencia al contenido de Cell<T>?
No, Cell no devuelve ninguna referencia, solo copia o actualiza el valor. Solo funciona con tipos Copy, para todos los demás use RefCell.
¿Qué pasará si se llama a borrow_mut dos veces consecutivas en el mismo RefCell?
Se producirá un pánico en tiempo de ejecución, ya que RefCell rastrea el número de referencias activas. El segundo intento de obtener acceso mutable cuando ya existe una referencia conducirá a un pánico.
En un proyecto, para almacenar cualquier estado mutable dentro de una estructura, siempre se utiliza RefCell, incluso cuando se puede usar propiedad mutable. El código se vuelve verboso, se producen pánicos en tiempo de ejecución y se vuelve difícil de probar.
Ventajas: Se puede evitar rápidamente las restricciones del compilador y lograr el comportamiento deseado.
Desventajas: Alto riesgo de errores en tiempo de ejecución, caída de la aplicación, difícil depuración de la lógica.
Se utiliza RefCell solo para implementar cachés perezosos en estructuras grandes, y en el resto del código permanecen con la propiedad y referencias clásicas. Todos los valores se limpian correctamente, no hay pánicos.
Ventajas: Lógica transparente, minimización del uso de mutabilidad interna, el código es robusto y predecible.
Desventajas: Requiere atención a los límites de modificación de datos: la lectura de la caché siempre es segura, la escritura solo por razones necesarias y en lugares estrechamente definidos.