Smart pointers de Rust explicados: Box, Rc, Arc y RefCell en 2026
Los smart pointers de Rust Box, Rc, Arc y RefCell explicados con ejemplos compilables de 2026, una tabla de decisión y las preguntas de entrevista más comunes.

Los smart pointers de Rust son las herramientas que habilitan patrones de propiedad que el borrow checker por sí solo no puede expresar: asignación en el heap, propiedad compartida y mutación detrás de una referencia compartida. Donde una referencia simple (&T) solo toma prestado un valor, un smart pointer posee sus datos y agrega comportamiento adicional encima. Esta guía desglosa los cuatro tipos que todo desarrollador de Rust encuentra en producción y en entrevistas: Box, Rc, Arc y RefCell, con ejemplos compilables orientados a la edición Rust 2024.
Usar Box<T> para asignación en el heap con un único propietario, Rc<T> para propiedad compartida en un solo hilo, Arc<T> para propiedad compartida entre hilos, y RefCell<T> para mutar un valor a través de una referencia compartida. Los pares Rc<RefCell<T>> y Arc<Mutex<T>> cubren el estado mutable compartido.
Qué son los smart pointers en Rust
Un smart pointer es una estructura que se comporta como un puntero pero transporta metadatos o capacidades adicionales. La mayoría implementa el trait Deref, de modo que *pointer y las llamadas a métodos funcionan como si el puntero fuera una referencia simple, y el trait Drop, de modo que la limpieza se ejecuta automáticamente cuando el valor sale del ámbito. La biblioteca estándar incluye los cuatro tipos que se cubren aquí, y comprenderlos depende de un dominio sólido de la propiedad y el préstamo, que deciden quién libera cada asignación y cuándo.
La distinción clave frente a una referencia es la propiedad. &T nunca posee los datos a los que apunta, así que no puede vivir más que el valor. Un smart pointer posee los datos, controla su tiempo de vida y los libera de forma determinista. El capítulo del Rust Book sobre smart pointers los trata como una categoría precisamente porque comparten esta forma de propiedad más comportamiento.
Box<T>: asignación en el heap para tipos recursivos y con tamaño
Box<T> es el smart pointer más simple. Almacena un valor en el heap y guarda un puntero a él en la pila, con un único propietario y sin sobrecarga en tiempo de ejecución más allá de la asignación misma. Su tarea más habitual es darles un tamaño conocido a los tipos recursivos: un tipo que se contiene a sí mismo de forma directa sería infinitamente grande, pero un Box es solo un puntero, así que su tamaño es fijo sin importar a qué apunte.
El ejemplo siguiente define un árbol binario. Cada Node contiene dos hijos, y sin indirección el compilador no puede calcular el tamaño de Tree. Envolver cada hijo en un Box rompe la recursión a nivel de tipo.
#[derive(Debug)]
enum Tree {
Leaf(i32),
// Box<Tree> puts each child on the heap, so Node has a fixed size (two pointers).
Node(Box<Tree>, Box<Tree>),
}
fn sum(tree: &Tree) -> i32 {
match tree {
Tree::Leaf(value) => *value, // base case: return the leaf
Tree::Node(left, right) => sum(left) + sum(right), // recurse into both children
}
}
fn main() {
// Build (1) + ((2) + (3)) = 6
let tree = Tree::Node(
Box::new(Tree::Leaf(1)),
Box::new(Tree::Node(
Box::new(Tree::Leaf(2)),
Box::new(Tree::Leaf(3)),
)),
);
println!("sum = {}", sum(&tree)); // sum = 6
}Box también importa cuando mover un valor grande resultaría costoso, o cuando una función devuelve un objeto de trait como Box<dyn Error>. En todos los casos la regla es la misma: un único propietario, liberado automáticamente cuando el Box se destruye.
Rc<T>: propiedad compartida en código de un solo hilo
A veces un valor necesita varios propietarios, y ninguno es claramente el último en usarlo. Rc<T> (contado por referencias) resuelve esto manteniendo un conteo fuerte de cuántos propietarios existen. Rc::clone incrementa ese conteo sin copiar los datos subyacentes, y cada destrucción lo decrementa. Cuando el conteo llega a cero, el valor se libera.
Considérese un objeto de configuración que varios workers leen. Cada worker debe conservar la configuración todo el tiempo que la necesite, y la asignación solo debe desaparecer cuando el último worker ya no está.
use std::rc::Rc;
#[derive(Debug)]
struct Config {
endpoint: String,
timeout_ms: u32,
}
fn main() {
// Rc::new moves Config onto the heap with a strong count of 1.
let config = Rc::new(Config {
endpoint: "https://api.example.com".to_string(),
timeout_ms: 5000,
});
// Rc::clone only bumps the reference count; it does not deep-copy Config.
let worker_a = Rc::clone(&config);
let worker_b = Rc::clone(&config);
// All three handles point to the same allocation.
println!("endpoint: {}", worker_a.endpoint);
println!("timeout: {}", worker_b.timeout_ms);
// strong_count reports how many owners currently hold the value.
println!("owners: {}", Rc::strong_count(&config)); // owners: 3
}Llamar a Rc::clone de forma explícita (en lugar de config.clone()) es idiomático: le indica al lector que la operación es barata y solo toca un contador. La trampa es que Rc<T> solo reparte referencias compartidas e inmutables. No puede mutar el valor que comparte, y no es seguro enviarlo entre hilos. La documentación de Rc detalla ambas restricciones.
RefCell<T> y la mutabilidad interior en Rust
Rust impone normalmente que un valor esté compartido mediante muchas referencias inmutables o mutado mediante exactamente una referencia mutable, y lo verifica en tiempo de compilación. RefCell<T> proporciona mutabilidad interior: permite que el código mute un valor a través de una referencia compartida trasladando esa misma verificación al tiempo de ejecución. borrow() devuelve una guarda de lectura compartida; borrow_mut() devuelve una guarda de escritura exclusiva. Las reglas son idénticas, pero las violaciones provocan un pánico en lugar de fallar en compilación.
Combinado con Rc, esto produce el patrón canónico de mutabilidad compartida en un solo hilo, Rc<RefCell<T>>: varios propietarios que pueden actualizar todos el mismo valor.
use std::rc::Rc;
use std::cell::RefCell;
// Rc gives shared ownership; RefCell allows mutation through a shared reference.
type SharedCounter = Rc<RefCell<u32>>;
fn increment(counter: &SharedCounter) {
// borrow_mut() hands out an exclusive reference, checked at runtime.
*counter.borrow_mut() += 1;
}
fn main() {
let counter: SharedCounter = Rc::new(RefCell::new(0));
let handle_a = Rc::clone(&counter);
let handle_b = Rc::clone(&counter);
increment(&handle_a);
increment(&handle_b);
increment(&counter);
// borrow() gives a shared read guard; the value is now 3.
println!("count = {}", counter.borrow()); // count = 3
}Llamar a borrow_mut() mientras otra guarda borrow() o borrow_mut() sigue viva provoca un pánico con already borrowed: BorrowMutError. Las garantías de seguridad se mantienen, pero un error de lógica que el compilador habría detectado se convierte en un fallo en tiempo de ejecución. Conviene mantener las guardas de vida corta y evitar retener un préstamo a través de una llamada a función que pueda reingresar al mismo RefCell.
La mutabilidad interior es también donde se esconden los ciclos de referencias. Dos valores Rc<RefCell<T>> que se apuntan mutuamente mantienen el conteo fuerte del otro por encima de cero para siempre, así que ninguno se libera nunca. La solución es Weak<T>, un handle sin propiedad que no afecta el conteo fuerte; el recorrido clásico está en Learn Rust With Entirely Too Many Linked Lists.
Arc<T>: conteo de referencias seguro entre hilos para la concurrencia
Arc<T> (contado por referencias de forma atómica) es el hermano multihilo de Rc<T>. La API pública es casi idéntica, pero el contador usa operaciones atómicas, de modo que clonar y destruir un Arc desde varios hilos a la vez sigue siendo correcto. Esa atomicidad es la razón por la que Arc se elige solo cuando el uso compartido cruza una frontera de hilo: el incremento atómico es notablemente más lento que el simple que usa Rc.
Como Arc<T> sigue repartiendo referencias inmutables, mutar entre hilos requiere una primitiva de sincronización. Mutex<T> es el compañero habitual, y da el patrón Arc<Mutex<T>> que refleja a Rc<RefCell<T>> en el código concurrente. El ejemplo lanza cuatro hilos que incrementan cada uno un total compartido mil veces.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc is safe to share across threads; Mutex serializes access to the inner value.
let total = Arc::new(Mutex::new(0u64));
let mut handles = Vec::new();
for _ in 0..4 {
// Each thread gets its own Arc handle (an atomic count bump).
let total = Arc::clone(&total);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
// lock() blocks until the mutex is free, then returns a guard.
let mut value = total.lock().unwrap();
*value += 1;
}
}));
}
for handle in handles {
handle.join().unwrap(); // wait for every thread before reading
}
println!("total = {}", *total.lock().unwrap()); // total = 4000
}El compilador impone la frontera por sí mismo: Rc<T> no es Send, así que intentar mover uno dentro de thread::spawn no compila, lo que empuja hacia Arc. Recurrir a Arc dentro de un solo hilo solo paga por atómicos que nunca se usan. La documentación de Arc cubre en detalle las garantías de ordenamiento de memoria, y Arc<Mutex<T>> sostiene buena parte del estado compartido en Rust asíncrono con Tokio.
¿Listo para aprobar tus entrevistas de Rust?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Box vs Rc vs Arc vs RefCell: cuándo usar cada uno
Los cuatro tipos se combinan a lo largo de dos ejes: cuántos propietarios tiene un valor, y si puede mutarse a través de un handle compartido. La tabla resume los compromisos.
| Tipo | Propiedad | Mutación vía handle compartido | Seguro entre hilos | Sobrecarga |
|------|-----------|----------------------------|-------------|----------|
| Box<T> | Único | No | Sí, si T: Send | Solo asignación en el heap |
| Rc<T> | Compartida | No | No | Conteo no atómico |
| Arc<T> | Compartida | No | Sí | Conteo atómico |
| RefCell<T> | Único | Sí (verificada en ejecución) | No | Bandera de préstamo en ejecución |
El árbol de decisión práctico es corto. Se necesita un único propietario en el heap: Box. Se necesitan varios propietarios en un solo hilo: Rc. Se necesitan varios propietarios entre hilos: Arc. Se necesita mutar un valor compartido: envolver el tipo interno en RefCell (un solo hilo) o Mutex (multihilo). La guía de Effective Rust sobre los tipos de referencia y puntero llega a la misma estratificación. Conviene empezar por la opción menos poderosa y agregar capacidad solo cuando el compilador lo obligue.
Preguntas de entrevista sobre smart pointers de Rust
Los smart pointers son un tema de entrevista confiable porque prueban si un candidato entiende la propiedad y no solo la sintaxis. Las preguntas siguientes aparecen con frecuencia, y hay más recopiladas en el módulo de entrevista sobre smart pointers de Rust.
¿Cuál es la diferencia entre Rc y Arc? Ambos proporcionan propiedad compartida mediante conteo de referencias. Rc usa un contador no atómico y está confinado a un solo hilo; Arc usa operaciones atómicas y puede compartirse entre hilos a un pequeño costo en ejecución. El compilador impone la división: Rc no es ni Send ni Sync, así que no puede cruzar una frontera de hilo.
¿Por qué combinar Rc con RefCell? Rc otorga propiedad compartida pero solo acceso inmutable. RefCell agrega mutabilidad interior, y permite que los propietarios muten el valor a través de una referencia compartida con verificaciones de préstamo en ejecución. Juntos, Rc<RefCell<T>> es el bloque idiomático de mutabilidad compartida en un solo hilo.
¿Cómo puede el conteo de referencias filtrar memoria en Rust? Dos valores Rc (o Arc) que se referencian entre sí forman un ciclo cuyos conteos fuertes nunca llegan a cero, así que la asignación nunca se libera. Romper el ciclo con Weak<T>, que mantiene una referencia sin propiedad, restaura la limpieza correcta.
¿Cuándo es preferible Box frente a Rc? Siempre que baste con un único propietario. Box no tiene sobrecarga de conteo de referencias, así que es la opción por defecto para asignación en el heap, tipos recursivos y objetos de trait. Solo conviene recurrir a Rc cuando se requiere propiedad compartida real.
Conclusión
Los smart pointers de Rust convierten la propiedad, de restricción en un conjunto de bloques combinables. La elección entre ellos se desprende directamente de la forma del problema:
- Recurrir a
Box<T>cuando un valor necesita un único propietario en el heap, cuando un tipo recursivo necesita un tamaño fijo, o cuando una función devuelve un objeto de trait. - Usar
Rc<T>para varios propietarios en un solo hilo, y cambiar aArc<T>en cuanto la propiedad cruza una frontera de hilo. - Agregar
RefCell<T>para mutabilidad interior en código de un solo hilo, yMutex<T>para el equivalente concurrente, manteniendo cortas las guardas de préstamo y de bloqueo. - Combinarlos de forma deliberada:
Rc<RefCell<T>>para estado mutable compartido en un solo hilo,Arc<Mutex<T>>entre hilos. - Vigilar los ciclos de referencias entre punteros contados y romperlos con
Weak<T>para evitar fugas. - Optar por defecto por el tipo menos poderoso y dejar que el compilador indique cuándo se necesita más capacidad.
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

Ownership y Borrowing en Rust: La Guia Definitiva para Dominarlo Todo
Ownership y borrowing en Rust explicados con codigo real. Semantica de movimiento, referencias, lifetimes y patrones del borrow checker para gestion segura de memoria en 2026.

Preguntas de Entrevista sobre Rust: Guia Completa 2026
Las 25 preguntas mas comunes en entrevistas sobre Rust. Ownership, borrowing, lifetimes, traits, async y concurrencia con respuestas detalladas y ejemplos de codigo.

Rust 2026 Edition: Traits, Generics y Preguntas Avanzadas de Entrevista Tecnica
Domina traits, generics y las novedades de Rust 2024/2026 Edition con ejemplos de codigo y preguntas frecuentes en entrevistas tecnicas avanzadas de Rust.