Rc y Arc en Rust: múltiples propietarios del mismo dato

Las reglas de ownership de Rust son claras: un valor tiene un único propietario. Pero a veces necesitas que varios partes del programa compartan el mismo dato sin copiarlo. Para eso existen Rc<T> (Reference Counted) y Arc<T> (Atomically Reference Counted): punteros inteligentes que permiten múltiples propietarios mediante un contador de referencias.

Rc: múltiples propietarios en un solo hilo

use std::rc::Rc;

fn main() {
    let dato = Rc::new(String::from("hola"));

    let ref1 = Rc::clone(&dato);  // incrementa el contador: 2
    let ref2 = Rc::clone(&dato);  // incrementa el contador: 3

    println!("{}", dato);    // "hola"
    println!("{}", ref1);    // "hola"
    println!("{}", ref2);    // "hola"
    println!("Referencias: {}", Rc::strong_count(&dato)); // 3
}
// Al salir del scope, el contador baja a 0 y el dato se libera

Rc::clone copia el puntero e incrementa el contador. No copia el dato. El dato se libera cuando el último Rc sale de scope.

Limitación importante: Rc no es seguro entre hilos (Send no implementado). Solo para un único hilo.

Rc con listas compartidas

use std::rc::Rc;

enum Lista {
    Cons(i32, Rc<Lista>),
    Nil,
}

fn main() {
    let cola = Rc::new(Lista::Cons(3, Rc::new(Lista::Nil)));

    // Dos listas comparten la misma cola sin copiarla
    let lista1 = Lista::Cons(1, Rc::clone(&cola));
    let lista2 = Lista::Cons(2, Rc::clone(&cola));

    println!("Cola compartida por {} listas", Rc::strong_count(&cola));
}

Arc: múltiples propietarios entre hilos

use std::sync::Arc;
use std::thread;

fn main() {
    let dato = Arc::new(vec![1, 2, 3, 4, 5]);

    let mut hilos = vec![];
    for _ in 0..3 {
        let dato_clon = Arc::clone(&dato); // solo copia el puntero
        let hilo = thread::spawn(move || {
            println!("Suma: {}", dato_clon.iter().sum::<i32>());
        });
        hilos.push(hilo);
    }

    for hilo in hilos {
        hilo.join().unwrap();
    }
}

Arc usa operaciones atómicas para el contador, que son seguras entre hilos pero tienen un pequeño coste respecto a Rc.

Rc vs Arc

CaracterísticaRc<T>Arc<T>
Seguro entre hilosNoSí
Coste del contadorMínimo (no atómico)Pequeño overhead atómico
Cuándo usarUn único hiloMúltiples hilos

Weak: evitar ciclos de referencias

Si dos Rc se apuntan mutuamente, el contador nunca llega a cero y hay una fuga de memoria. La solución es Weak: una referencia que no incrementa el contador fuerte.

use std::rc::{Rc, Weak};
use std::cell::RefCell;

struct Nodo {
    valor: i32,
    padre: Option<Weak<RefCell<Nodo>>>,  // Weak: no aumenta strong_count
    hijos: Vec<Rc<RefCell<Nodo>>>,
}

fn acceder_padre(nodo: &Rc<RefCell<Nodo>>) {
    if let Some(padre) = &nodo.borrow().padre {
        if let Some(p) = padre.upgrade() { // upgrade: Weak ? Option<Rc>
            println!("Padre: {}", p.borrow().valor);
        }
    }
}

Arc con Mutex para escritura concurrente

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let contador = Arc::new(Mutex::new(0));

    let mut hilos = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&contador);
        hilos.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }

    for h in hilos { h.join().unwrap(); }
    println!("Contador: {}", *contador.lock().unwrap()); // 5
}

Resumen

  • Rc<T>: múltiples propietarios en un único hilo. Contador no atómico.
  • Arc<T>: múltiples propietarios entre hilos. Contador atómico.
  • Rc::clone / Arc::clone: copian el puntero, no el dato.
  • Weak<T>: referencia sin propietario para evitar ciclos.
  • Arc + Mutex: el patrón estándar para datos mutables compartidos entre hilos.

El siguiente artículo cubre RefCell<T>: mutabilidad interior cuando el borrow checker en tiempo de compilación es demasiado restrictivo para patrones como mock objects y grafos.

COMPARTE ESTE ARTÍCULO

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