HeadlinesBriefing favicon HeadlinesBriefing.com

Visualizando las Vtables de Rust: Cómo funciona dyn Trait en memoria

Hacker News •
×

Me adentro en Rust y es a la vez satisfactorio y desconcertante. Hasta ahora he estado aprendiendo del libro y el libro de Mara Bos, pero me picó el deseo de disecarlo yo mismo. Mi objetivo inicial con estos experimentos era comparar el enfoque de Rust en la polimorfismo con el de C++. Sin embargo, al final, como me he dado cuenta, es un poco una trampa cuando intentas entender un nuevo lenguaje a través de otro para intentar establecer paralelismos 1:1. Puede parecer que ayuda, pero al final del día, no podemos tratar a Rust como C++ con sintaxis diferente. Si fuera así, no habría nada revolucionario al respecto. Dicho esto, creo que hay mérito en indagar y venir a entender el por qué. Así que, si eres como yo y necesitas saber exactamente qué está sucediendo en memoria, para sentir que realmente entiendes los conceptos, espero que encuentres útil esta publicación :)Por cierto, la imagen de miniatura es una foto del hongo de la roya, a quien debemos el nombre de Rust. Credit: gailhampshire from Cradley, Malvern, U.K, CC BY 2.0, via Wikimedia Commons. Puedes encontrar todo el código y los experimentos en Git Hub.

Introducción: El núcleo de lo que importa

Lo que estamos intentando lograr es bastante simple. Digamos que tenemos un montón de formas: círculos, cuadrados, triángulos, y queremos llamar a draw() en cada una.

Enfoque C++ #1: Funciones virtuales La primera forma que se nos ocurre en C++ para hacer esto es a través de funciones virtuales, que hacen uso del polimorfismo en tiempo de ejecución. El puntero a la tabla virtual vive dentro del objeto, la resolución virtual ocurre automáticamente.

std::vector<Shape*> shapes = { new Circle(), new Square() };

for (auto* s : shapes)s->draw();

El equivalente en Rust es dyn Trait, que es lo que finalmente queremos entender. Pero primero, echemos un vistazo a otra forma en que podríamos resolver esto en C++.

Enfoque C++ #2: CRTPTambién se puede tomar el camino de CRTP (Patrón de plantilla recurrente curioso), que es esencialmente polimorfismo en tiempo de compilación. Si te interesa, esta increíble charla de Klaus Iglberger fue mi primera introducción al tema, y la que sigo consultando para referencia.

template<typename Derived>struct Shape {void draw() {static_cast<Derived*>(this)->draw();}}; En esencia, no hay tablas virtuales y se resuelve en tiempo de compilación, sacrificando legibilidad (realmente es un montón de palabras).

Rust ofrece una mucho más directa y sencilla equivalente a CRTP, a saber, la singleta. Esta es la aproximación que profundizaremos primero para comenzar a construir nuestro modelo mental de lo que Rust tiene que ofrecer.

Despacho estático Photo by Jiawei Zhao on Unsplash El despacho estático, también conocido como genéricos, logra un resultado similar al CRTP: el compilador genera una copia separada de la función para cada tipo con el que se llama. No hay costo en tiempo de ejecución, pero los tipos deben conocerse en tiempo de compilación.

trait Draw {fn draw(&self) -> &str;}struct Circle;struct Square;impl Draw for Circle {fn draw(&self) -> &str {