Rc<T> (Reference Counted) es un puntero inteligente que permite tener múltiples dueños de un mismo valor ubicado en el heap. En Rust, la regla de oro es que cada valor tiene un único dueño; sin embargo, en estructuras de datos complejas como grafos o árboles, es común que un mismo nodo sea referenciado por múltiples entidades. Rc<T> resuelve esto mediante un contador de referencias almacenado en el propio objeto en el heap.
El funcionamiento es sencillo: al crear un valor con Rc::new, el contador se inicializa en 1. Cuando se utiliza Rc::clone(&rc), no se está duplicando el dato original (lo cual sería costoso), sino que se crea una nueva copia del puntero e incrementa el contador en una operación muy económica. El valor solo se libera de la memoria cuando el contador de referencias llega a cero, es decir, cuando todas las instancias de Rc han salido de su ámbito (scope).
Sin embargo, este modelo presenta un riesgo crítico: los ciclos de referencias. Si el Objeto A contiene un Rc hacia el Objeto B, y el Objeto B contiene un Rc hacia el Objeto A, sus contadores nunca llegarán a cero, incluso si el resto del programa ya no los utiliza. Esto provoca una fuga de memoria (memory leak). Para evitar esto, Rust proporciona Weak<T>, que permite referenciar un valor sin aumentar el contador fuerte, permitiendo que el ciclo se rompa y la memoria se libere correctamente. Además, dado que Rc<T> no implementa los rasgos Send ni Sync, su uso está estrictamente limitado a entornos de un solo hilo (single-threaded).
use std::rc::Rc;
use std::rc::Weak;
use std::cell::RefCell;
// Un nodo que requiere mutabilidad interior para actualizar su padre
struct Nodo {
id: i32,
// Usamos Weak para evitar ciclos de referencia que causen fugas
padre: RefCell<Weak<Nodo>>,
}
fn main() {
// 1. Creación del nodo raíz con Rc
// Rc::new coloca el Nodo en el heap con un contador de 1
let nodo_padre = Rc::new(Nodo {
id: 1,
padre: RefCell::new(Weak::new()),
});
println!("Contador de nodo_padre al inicio: {}", Rc::strong_count(&nodo_padre));
// 2. Compartir la propiedad mediante clonación
// Rc::clone incrementa el contador de nodo_padre a 2
let nodo_hijo = Rc::clone(&nodo_padre);
println!("Contador de nodo_padre tras clonar a nodo_hijo: {}", Rc::strong_count(&nodo_padre));
// 3. Establecer una referencia débil (Weak) para evitar el ciclo de ownership
// Usamos borrow_mut() porque el campo 'padre' es un RefCell para permitir mutabilidad interior
*nodo_hijo.padre.borrow_mut() = Rc::downgrade(&nodo_padre);
// 4. Recuperar la referencia fuerte desde el Weak
// upgrade() devuelve Option<Rc<T>>: Some si el dato aún existe, None si ya se liberó
if let Some(padre_recuperado) = nodo_hijo.padre.borrow().upgrade() {
println!("Acceso exitoso al padre (ID: {}): {}", padre_recuperado.id, padre_recuperado.id);
}
// 5. El contador se mantiene en 1 para nodo_padre porque nodo_hijo es un Rc fuerte
println!("Contador final de nodo_padre: {}", Rc::strong_count(&nodo_padre));
}
Explicación del Código
nodo_padre: Es una instancia deRc<Nodo>. Al inicializarse conRc::new, se asigna un contador de referencias de 1.Rc::strong_count(&nodo_padre): Esta función inspecciona el contador interno denodo_padre. En la primera llamada devuelve1.nodo_hijo: Se obtiene medianteRc::clone(&nodo_padre). Es un nuevo puntero hacia el mismo espacio de memoria en el heap, lo que incrementa el contador denodo_padrea2.nodo_hijo.padre: Es unRefCell<Weak<Nodo>>. UsamosRefCellpara aplicar mutabilidad interior, permitiéndonos modificar el campopadreincluso cuando tenemos una referencia compartida.Rc::downgrade(&nodo_padre): Crea un objetoWeak<Nodo>. Este puntero no aumenta el contador fuerte, lo que permite quenodo_padrepueda ser destruido sinodo_padreynodo_hijosalen de ámbito.nodo_hijo.padre.borrow().upgrade(): El métodoupgrade()intenta convertir la referencia débil (Weak) en una referencia fuerte (Rc). Devuelve unOption<Rc<Nodo>>porque el valor original podría haber sido liberado previamente.padre_recuperado: Es elRc<Nodo>obtenido tras unupgradeexitoso, permitiendo acceder a los campos denodo_padre.
N° 54