BookinglyTech News
Software

Rust no sabe coercionar un &str a dyn Any y la solución es construir la vtable a mano

El type switch de Go sobre interface{} no tiene equivalente directo en Rust: la coerción a un objeto dyn exige tipos con tamaño conocido, y para un &str hay que fabricar la vtable en tiempo de ejecución.

2 min de lecturaLobsters0 vistas

Un type switch sobre interface{} en Go permite preguntar por el tipo, bajar al concreto y operar con el valor. El equivalente en Rust sería dyn Any con downcast_ref, y ahí aparece el primer tropiezo: la coerción a un objeto dyn solo funciona con tipos Sized. Un &str no tiene tamaño conocido en compilación, así que pasarlo a una función que recibe &dyn Any obliga a copiar la cadena a un String o a exigir una vida 'static.

Eso rompe el objetivo de doit, la función que en el ejemplo de Go consume el valor sin argumentos genéricos. Y no es un capricho: hay contextos donde los genéricos no valen, como hacer un trait compatible con dyn, exportar una función como extern o pasarla como objeto fn/Fn. En esos casos hace falta un dyn de verdad.

Por qué el compilador se planta

Al aplicar la coerción, Rust sintetiza la metadata del puntero para el dyn Any: un &'static VTable que vive en .rodata y que el compilador deriva del tipo en tiempo de compilación. No puede usar el valor. El problema está en uno de los cuatro campos que componen esa vtable, colocados como en un #[repr(C)]: la función de drop, el tamaño, la alineación y la función de type_id. El tamaño de un str depende de su longitud en tiempo de ejecución, así que no se puede derivar en compilación.

Hay otro detalle que se escapa fácil: los punteros a función de la vtable no reciben la metadata del puntero original. Cuando el compilador genera la llamada a type_id, quita la metadata y pasa solo el puntero fino. Para calcular un TypeId da igual, pero la longitud de la cadena se pierde por el camino.

Construir la vtable a mano

La salida es fabricarla en tiempo de ejecución, porque ahí sí se conoce el tamaño real. El autor define un AnyVTable con #[repr(C)], reserva sitio en la pila con MaybeUninit<AnyVTable> dentro de un struct Host, y un método borrow que devuelve &dyn Any. La vida del préstamo garantiza que la vtable sigue viva mientras se use la referencia; con std::ptr::metadata() en nightly se puede extraer la metadata sin arrastrar el lifetime.

La propuesta que cierra el texto es un ajuste al lenguaje: un lifetime para la vtable después de dyn, algo como dyn<'s> Any, que por defecto se comportaría como el actual 'static en unos contextos e introduciría uno nuevo en las firmas de función.

Merece la pena tenerlo a mano si se trabaja con Any desde código genérico o unsafe: deja claro que los mecanismos reflexivos de Go y Rust no son intercambiables, y qué se paga cuando se fuerza la comparación.