Rc> en Rust: el patrón para datos mutables con múltiples propietarios

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.
  • Weak rompe los ciclos de referencia en árboles y grafos.
  • Evita el double borrow: no mantengas un borrow() activo mientras intentas borrow_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.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP