Por qué el «recolector de basura» en Rust no es lo que piensas
\n\nSi vienes del mundo de Python, Java o C#, la palabra «recolector de basura» (garbage collector) evoca una imagen clara: un entorno en segundo plano que periódicamente encuentra y elimina objetos a los que ya nadie hace referencia. En Rust es diferente. Aquí no hay un GC clásico, pero existe un sistema de propiedad y préstamo que resuelve la misma tarea —gestionar el tiempo de vida de los objetos— en tiempo de compilación.
\n\nSin embargo, la vida (y el código) es más compleja que los esquemas ideales. A veces necesitas compartir la propiedad de un objeto entre varias partes del programa, crear referencias cíclicas o trabajar con datos mutables a través de punteros inmutables. Es aquí donde entran en escena los objetos garbage-collected en Rust: Rc, Arc, Weak y RefCell. Analicemos cómo gestionar su tiempo de vida, evitar fugas de memoria y no volvernos locos.
Este artículo es una guía detallada para quienes quieren entender cómo funciona la gestión del tiempo de vida de objetos en Rust en la práctica. No solo enumeraremos los tipos, sino que mostraremos escenarios reales, incluyendo una pregunta popular de Stack Overflow (sí, esa con miles de visitas).
\n\nConceptos clave: propiedad, préstamo y contador de referencias
\n\nAntes de sumergirnos en profundidad, recordemos lo básico:
\n\n- \n
- Propiedad (ownership) — cada valor tiene exactamente un propietario. Cuando el propietario sale del ámbito, el valor se elimina. \n
- Préstamo (borrowing) — puedes «tomar prestada» temporalmente una referencia a un valor sin convertirte en propietario. Restricciones: o una referencia mutable, o tantas inmutables como quieras. \n
- Contador de referencias (reference counting) — es un mecanismo que permite tener múltiples propietarios de un mismo valor. En Rust se implementa mediante
Rc(para programas de un solo hilo) yArc(para programas multihilo). \n
Cuando hablamos de «objetos garbage-collected» en el contexto de Rust, nos referimos precisamente a objetos gestionados por un contador de referencias. No se eliminan inmediatamente al salir del ámbito, sino que esperan hasta que el contador llegue a cero. Esto es la gestión del tiempo de vida — tú controlas cuándo se liberará la memoria.
\n\nRc y Arc: la base de la gestión del tiempo de vida
\n\nRc (Reference Counted) es un puntero inteligente que permite compartir la propiedad de un mismo valor entre varias partes del código en un solo hilo. Cada vez que clonas un Rc, el contador aumenta. Cuando el clon sale del ámbito, disminuye. Cuando el contador llega a cero, el valor se elimina.
Ejemplo de uso simple de Rc:
use std::rc::Rc;\n\nfn main() {\n let data = Rc::new(vec![1, 2, 3]);\n let a = Rc::clone(&data);\n let b = Rc::clone(&data);\n\n println!("Contador de referencias: {}", Rc::strong_count(&data)); // 3\n\n drop(a);\n println!("Después de eliminar a: {}", Rc::strong_count(&data)); // 2\n}\n\n\nAquí data, a y b son tres propietarios de un mismo vector. Mientras al menos uno de ellos exista, el vector permanece en memoria. Esto es ideal para situaciones en las que no puedes determinar qué módulo terminará su ejecución en último lugar.
Arc (Atomic Reference Counted) funciona igual, pero utiliza operaciones atómicas, lo que lo hace seguro para programas multihilo. El costo es una ligera disminución del rendimiento.
use std::sync::Arc;\nuse std::thread;\n\nfn main() {\n let data = Arc::new(vec![1, 2, 3]);\n let mut handles = vec![];\n\n for _ in 0..3 {\n let data_clone = Arc::clone(&data);\n handles.push(thread::spawn(move || {\n println!("{:?}", data_clone);\n }));\n }\n\n for handle in handles {\n handle.join().unwrap();\n }\n}\n\n\n