Visualizando las vtables de Rust y el funcionamiento de dyn Trait en memoria
Un autor explora cómo Rust implementa la polimorfismo dinámico comparándolo con los enfoques tradicionales de C++ y muestra el comportamiento de los tipos de tamaño cero.

El artículo de Sofía Belén, publicado en Lobsters el 5 de septiembre de 2026, muestra paso a paso cómo Rust representa los dyn Trait en memoria y cómo se diferencia de los mecanismos de polimorfismo de C++. El objetivo es que los desarrolladores que ya manejan virtual functions o CRTP en C++ comprendan qué ocurre bajo el capó cuando se usa dyn Trait.
De C++ a Rust: dos caminos para la polimorfismo
En C++ la solución más conocida es usar funciones virtuales. Cada objeto contiene un puntero a una tabla de funciones (vtable) y el despacho ocurre en tiempo de ejecución:
std::vector<Shape*> shapes = { new Circle(), new Square() };
for (auto* s : shapes) s->draw();
Otro enfoque es el CRTP, que genera código estático sin vtables, pero sacrifica legibilidad. Rust ofrece una alternativa directa al CRTP mediante la monomorfización (despacho estático) y, para el caso dinámico, el tipo dyn Trait.
Despacho estático en Rust
Con genéricos, el compilador crea una versión de la función para cada tipo que implemente el trait:
trait Draw { fn draw(&self) -> &str; }
struct Circle; struct Square;
impl Draw for Circle { fn draw(&self) -> &str { "Drawing a circle" } }
impl Draw for Square { fn draw(&self) -> &str { "Drawing a square" } }
fn draw_shape<T: Draw>(shape: T) { println!("{}", shape.draw()); }
El compilador genera draw_shape::<Circle> y draw_shape::<Square> sin costo en tiempo de ejecución, pero obliga a declarar explícitamente el contrato mediante el trait.
¿Qué ocurre con los tipos de tamaño cero?
Al inspeccionar std::mem::size_of::<Circle>() el resultado es 0, a diferencia de C++ donde todo objeto ocupa al menos un byte. En Rust los Zero‑Sized Types (ZST) no almacenan datos; la identidad se gestiona a través del sistema de propiedad y el borrow‑checker, no mediante direcciones de memoria.
Si se toma la dirección de dos variables ZST en modo debug, el compilador les asigna slots de un byte solo para que el depurador pueda mostrarlas:
let a = Circle; let b = Circle;
println!("{:p}", &a as *const Circle);
println!("{:p}", &b as *const Circle);
Salida típica: 0x7ffdda99aece y 0x7ffdda99aecf. En una compilación de release esas direcciones no aparecen, confirmando que el espacio es ficticio.
Comparación final
- Despacho estático – Rust genera código especializado, similar al CRTP, pero con una sintaxis de trait más clara.
- Despacho dinámico –
dyn Traitusa una vtable implícita; el puntero a la vtable vive fuera del objeto, a diferencia del puntero interno que C++ coloca dentro del objeto. - Identidad – En C++ la identidad se basa en la dirección de memoria; en Rust la propiedad y el borrow‑checker garantizan la unicidad sin necesidad de espacio adicional.
Este análisis ayuda a decidir cuándo usar genéricos y cuándo optar por dyn Trait, especialmente en sistemas donde el overhead de una vtable es crítico.
En conclusión, comprender la representación de dyn Trait y los ZST permite a los equipos de Rust optimizar la arquitectura de sus aplicaciones, evitando sorpresas de rendimiento y aprovechando al máximo las garantías de seguridad del lenguaje.
