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Ãstica | Rc<T> | Arc<T> |
|---|---|---|
| Seguro entre hilos | No | SÃ |
| Coste del contador | MÃnimo (no atómico) | Pequeño overhead atómico |
| Cuándo usar | Un único hilo | Mú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.
