La combinación Rc<RefCell<T>> es el patrón estándar de Rust para datos que necesitan ser compartidos por múltiples propietarios y modificados. Rc gestiona la propiedad compartida; RefCell permite la mutación a través de referencias inmutables. Juntos resuelven casos que el borrow checker estático no puede manejar directamente.
Contador compartido
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let contador = Rc::new(RefCell::new(0));
let c1 = Rc::clone(&contador);
let c2 = Rc::clone(&contador);
let c3 = Rc::clone(&contador);
*c1.borrow_mut() += 1;
*c2.borrow_mut() += 1;
*c3.borrow_mut() += 1;
println!("Contador: {}", contador.borrow()); // 3
}
Árbol con referencias al padre
use std::rc::{Rc, Weak};
use std::cell::RefCell;
struct Nodo {
valor: i32,
padre: Option<Weak<RefCell<Nodo>>>,
hijos: Vec<Rc<RefCell<Nodo>>>,
}
impl Nodo {
fn nuevo(valor: i32) -> Rc<RefCell<Nodo>> {
Rc::new(RefCell::new(Nodo {
valor,
padre: None,
hijos: vec![],
}))
}
fn añadir_hijo(padre: &Rc<RefCell<Nodo>>, hijo: &Rc<RefCell<Nodo>>) {
hijo.borrow_mut().padre = Some(Rc::downgrade(padre));
padre.borrow_mut().hijos.push(Rc::clone(hijo));
}
}
fn main() {
let raiz = Nodo::nuevo(1);
let hijo1 = Nodo::nuevo(2);
let hijo2 = Nodo::nuevo(3);
Nodo::añadir_hijo(&raiz, &hijo1);
Nodo::añadir_hijo(&raiz, &hijo2);
println!("Raíz tiene {} hijos", raiz.borrow().hijos.len()); // 2
// Acceder al padre desde el hijo
if let Some(padre) = &hijo1.borrow().padre {
if let Some(p) = padre.upgrade() {
println!("Padre del hijo1: {}", p.borrow().valor); // 1
}
}
}
Grafo con aristas compartidas
use std::rc::Rc;
use std::cell::RefCell;
type Enlace = Option<Rc<RefCell<NodoGrafo>>>;
struct NodoGrafo {
id: u32,
vecinos: Vec<Rc<RefCell<NodoGrafo>>>,
}
fn conectar(a: &Rc<RefCell<NodoGrafo>>, b: &Rc<RefCell<NodoGrafo>>) {
a.borrow_mut().vecinos.push(Rc::clone(b));
b.borrow_mut().vecinos.push(Rc::clone(a));
}
Cómo evitar el pánico por double borrow
use std::cell::RefCell;
fn procesar(celda: &RefCell<Vec<i32>>) {
// MAL: si iteras con borrow() y luego intentas borrow_mut() ? PANIC
// for item in celda.borrow().iter() {
// celda.borrow_mut().push(*item); // PANIC
// }
// BIEN: clona primero para separar el borrow
let copia: Vec<i32> = celda.borrow().clone();
for item in copia.iter() {
celda.borrow_mut().push(*item);
}
}
Cuándo usar Arc+Mutex en lugar de Rc+RefCell
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc + Mutex para datos compartidos entre hilos
let datos = Arc::new(Mutex::new(vec![1, 2, 3]));
let datos2 = Arc::clone(&datos);
let hilo = thread::spawn(move || {
datos2.lock().unwrap().push(4);
});
hilo.join().unwrap();
println!("{:?}", datos.lock().unwrap()); // [1, 2, 3, 4]
}
Resumen
Rc<RefCell<T>>: múltiples propietarios en un hilo con mutabilidad interior.Weakrompe los ciclos de referencia en árboles y grafos.- Evita el double borrow: no mantengas un
borrow()activo mientras intentasborrow_mut(). - Para múltiples hilos:
Arc<Mutex<T>>. - Estos patrones son el último recurso cuando el borrow checker estático es insuficiente.
El siguiente artículo introduce la concurrencia en Rust: cómo crear hilos con thread::spawn, usar move closures para transferir datos y esperar con join.
