La mutabilidad interior es un patrón de diseño que permite modificar datos incluso cuando se tiene una referencia inmutable (&T) a ellos. En Rust, el borrow checker impone reglas estrictas en tiempo de compilación para garantizar la seguridad de la memoria, prohibiendo tener múltiples referencias mutables o una mutable junto a otras referencias. Sin embargo, existen casos legítimos donde necesitamos mutar algo internamente, como contadores de visitas en una estructura compartida o cachés de valores calculados, sin cambiar la interfaz pública de la estructura.
Para lograr esto, Rust ofrece dos mecanismos principales que desplazan la verificación de las reglas de préstamo del tiempo de compilación al tiempo de ejecución (runtime):
-
Cell<T>: Se utiliza para tipos que implementan el traitCopy. Su diseño es extremadamente seguro porque no devuelve referencias a los datos internos. El métodoget()devuelve una copia del valor, yset(nuevo_valor)reemplaza el contenido. Al trabajar con copias, no hay riesgo de que se rompan las reglas de exclusividad de referencias, ya que no se está entregando un puntero al interior de laCell. -
RefCell<T>: Se utiliza para tipos que no implementanCopy(comoStringoVec). A diferencia deCell,RefCellpermite obtener referencias reales mediante los métodosborrow()(que devuelve unRef<T>, equivalente a&T) yborrow_mut()(que devuelve unRefMut<T>, equivalente a&mut T). El coste de esta flexibilidad es que la validación de las reglas de préstamo ocurre en tiempo de ejecución. Si intentas pedir unborrow_mut()mientras ya existe unborrow(), el programa lanzará unpanic!. Para manejar esto de forma segura sin interrumpir la ejecución, se pueden usartry_borrow()ytry_borrow_mut(), que devuelven unResult.
Es crucial entender que ambos tipos no son seguros para entornos multihilo (!Sync), por lo que su uso debe limitarse a escenarios de un solo hilo.
use std::cell::{Cell, RefCell};
struct MonitorSistema {
// Cell se usa para tipos Copy (u32)
contador_errores: Cell<u32>,
// RefCell se usa para tipos no-Copy (String)
log_ultimo_evento: RefCell<String>,
}
fn main() {
let monitor = MonitorSistema {
contador_errores: Cell::new(0),
log_ultimo_evento: RefCell::new(String::from("Sistema iniciado")),
};
// 1. Uso de Cell: Modificación simple sin necesidad de &mut
// Solo necesitamos una referencia inmutable a 'monitor'
monitor.contador_errores.set(monitor.contador_errores.get() + 1);
monitor.contador_errores.set(5); // Seteo directo
println!("Errores detectados: {}", monitor.contador_errores.get());
// 2. Uso de RefCell: Gestión de referencias en runtime
{
// borrow_mut() devuelve un RefMut<String>
let mut log = monitor.log_ultimo_evento.borrow_mut();
log.push_str(" - Error detectado en el núcleo");
// Al salir de este scope, 'log' se libera
}
println!("Log actual: {}", monitor.log_ultimo_evento.borrow());
// 3. Manejo de errores con try_borrow_mut
// Creamos un préstamo mutuo que se mantiene vivo
let _prestamo_mutuo = monitor.log_ultimo_evento.borrow_mut();
// Intentamos un préstamo de lectura mientras existe el préstamo mutuo
// Si usáramos .borrow(), el programa haría panic!
match monitor.log_ultimo_evento.try_borrow() {
Ok(log) => println!("Lectura exitosa: {}", log),
Err(_) => println!("Error: No se pudo leer, hay un préstamo mutuo activo."),
}
}
Análisis del Código
monitor.contador_errores: Es de tipoCell<u32>. En la líneamonitor.contador_errores.get(), se obtiene una copia del valor actual (gracias a queu32esCopy). Luego,set()actualiza el valor interno de la celda. No se requiere una referencia mutable al structMonitorSistema.monitor.log_ultimo_evento: Es de tipoRefCell<String>. ComoStringno esCopy, no podemos usarCell.monitor.log_ultimo_evento.borrow_mut(): Este método retorna unRefMut<String>. Este objeto inteligente actúa como una referencia mutable (&mut String) a la cadena de texto. Mientras este objeto esté en memoria, cualquier otro intento de préstamo mutuo o de lectura fallará.monitor.log_ultimo_evento.borrow(): Retorna unRef<String>, que permite la lectura del contenido delRefCell.monitor.log_ultimo_evento.try_borrow(): Es la versión segura deborrow(). En el ejemplo, como_prestamo_mutuomantiene un préstamo mutuo activo,try_borrow()detecta la violación de reglas de préstamo en tiempo de ejecución y devuelve unErren lugar de causar unpanic!.
N° 56