Si reescribiera Rust
(josephg.com)- Rust parte de una preocupación clara: aunque destaca por su seguridad de memoria, rendimiento, tipos algebraicos y las ventajas de Cargo, la evolución del lenguaje en el canal estable se ha vuelto lenta, por lo que se siente como “un producto de primera generación que aún no está terminado”
- Después de que la comunidad creciera, la toma de decisiones por consenso se volvió un cuello de botella, y siguen acumulándose funciones como las corutinas que, aunque ya están implementadas, no pueden usarse por mucho tiempo en stable Rust
- La hipotética “seph edition” mantendría el ecosistema actual de Rust y Cargo, pero permitiría breaking changes para experimentar con sistemas de efectos, permisos en tiempo de compilación, borrow de campos de structs, comptime y más
- Para reducir el riesgo de la cadena de suministro, habría que asociar capabilities a funciones sensibles como escritura de archivos, red, FFI y
unsafe, y exigir que quien llama las autorice explícitamente - La mayoría de las propuestas no son compatibles con Rust actual, y hasta agregar solo capabilities introduce nuevas condiciones de fallo que rompen compatibilidad semver, por lo que haría falta una nueva edition o un fork del compilador
Por qué Rust se siente como “un producto de primera generación”
- Al principio, Rust resulta muy atractivo por sus tipos algebraicos, seguridad de memoria sin costo de rendimiento y un gestor de paquetes moderno
- Después de usarlo durante unos 4 años, da la impresión de que el lenguaje permanece “siempre un poco incompleto”
- La queja central es que se redujo la frecuencia con la que llegan nuevas funciones a stable Rust y que la velocidad de evolución del lenguaje también cayó mucho
- El Rust unstable book enumera alrededor de 700 unstable features, muchas de ellas cambios en la biblioteca estándar
- Coroutines es una función cuyo RFC tiene 7 años; ya está implementada en el compilador, pero quienes usan stable Rust todavía no pueden aprovecharla
- El proceso de RFC de Rust parece un lugar donde las buenas ideas se quedan detenidas por mucho tiempo
- Se menciona como ejemplo la discusión para mejorar
Mutex: 25 personas dejaron más de 200 comentarios durante 2 años, sin llegar a una conclusión clara
- Se menciona como ejemplo la discusión para mejorar
La idea de una hipotética “seph edition”
- Se imagina hacer un fork del compilador, dejar intacto el código Rust existente y agregar una Rust edition separada llamada “seph”
- En esa edition se permitirían breaking changes, pero si el compilador pudiera seguir compilando Rust mainline, entonces todavía podrían usarse los crates existentes de Cargo
- Los cambios deseados se agrupan en cinco ejes principales
- traits de funciones y sistema de efectos
- capabilities en tiempo de compilación
- rediseño de
Pin, move y borrow de structs comptimeal estilo Zig- pequeñas mejoras de sintaxis y de la biblioteca estándar
Traits de funciones y sistema de efectos
- Rust tiene traits para structs, pero aquí se propone que también se puedan asociar traits/effects más ricos a las funciones
- Las propiedades que una función podría tener se resumen así
- si puede panic o no
- si tiene tamaño de stack fijo
- si siempre llega al final o si hace
yieldoawait - en caso de ser coroutine, su tipo de continuation
- si es una función pura
- si ejecuta código
unsafeindirectamente - si garantiza terminación
- Si los parámetros y el tipo de retorno de una función se expusieran como associated types de la función, sería posible hacer referencias de tipos que hoy no se pueden expresar en stable Rust
- El ejemplo usa código que nombra directamente el tipo de retorno de una función, como
some_iter::Output, para ponerlo dentro de un campo de struct - En casos como el kernel de Linux, donde hace falta garantizar que cierto bloque de código jamás haga panic, una anotación como
#[disallow(Panic)]podría verificar la posibilidad de panic incluso a través de llamadas recursivas - El compilador ya maneja traits relacionados con funciones como
Fn,FnOnceyFnMut, pero se considera que su forma actual es demasiado limitada - Como referencia más detallada se enlaza el artículo y charla de Yoshua Wuyts sobre sistemas de efectos
Reducir el riesgo de la cadena de suministro con capabilities en tiempo de compilación
- La mayoría de los proyectos en Rust incorporan muchos crates de terceros, y hasta un crate utilitario pequeño puede introducir riesgo en la cadena de suministro mediante una actualización maliciosa
- Así como
unsaferequiere un opt-in explícito en el ámbito de la seguridad de memoria, también debería exigirse permiso explícito para funciones sensibles relacionadas con sistema de archivos, red, FFI y raw pointers - Se propone poner marker tags en las funciones sensibles de la biblioteca estándar
- Ejemplo:
std::fs::write(path, contents)escribe archivos en rutas arbitrarias, así que tendría una etiqueta como#[cap(fs_write)] - El compilador marcaría automáticamente con taint todo el árbol de llamadas que invoque esa función
- Ejemplo:
- Si un crate de terceros requiere la capability
fs_write, quien lo llama tendría que permitirla explícitamente enCargo.tomlo con una anotación en el punto de llamada - Si no se autoriza, el compilador podría emitir un error indicando que
foo::do_stuff()escribe en el sistema de archivos local, pero que el cratefoono es de confianza para esa capability - Muchos crates utilitarios como
human-sizeoserdeno necesitan capabilities especiales, así que aunque su autor agregara código malicioso, la compilación podría bloquear cualquier intento de escribir archivos o usar la red - Como alternativa, también se plantea modificar las APIs sensibles para que reciban un parámetro
Capabilityseparado- Ejemplo: que
std::fs::writerequiera un valorFsWriteCapability - La creación de objetos
Capabilityestaría restringida para que solo pudiera hacerla el crate raíz
- Ejemplo: que
- Este enfoque agrega más boilerplate, pero es más flexible, y algo similar también haría falta para scripts
build.rsy bloquesunsafe - Incluso si hackearan crates.io y se inyectara código tipo cryptolocker en
serde, con este modelo de capabilities aparecería un error de compilación antes de que ese código pudiera ejecutarse en las máquinas de varios desarrolladores o terminar incluido en el binario
Rediseño de Pin, move y borrow de structs
Pinse describe como un hack complejo para esquivar huecos del borrow checker, una especie de parche surgido por preservar compatibilidad hacia atrás- Lo que en realidad haría falta se parece más a un marker trait
Movepara indicar tipos que sí pueden moverse - En Rust actual no existe un trait
Pin; lo que existen sonUnpiny!Unpin, y esa doble negación vuelve el concepto más difícil de entender - También se critica que
Pinsolo se aplique a tipos por referencia, lo que lleva a envolver código enBoxpor todas partes- Como ejemplos se enlazan un wrapper de streams de Tokio y bibliotecas helper como
ouroboros,async-traityself_cell
- Como ejemplos se enlazan un wrapper de streams de Tokio y bibliotecas helper como
- Las funciones que reciben valores fijados, como
Future::poll(self: Pin<&mut Self>, ..), también se vuelven más complejas, y hacen falta crates extra para manejar projection - Dentro de una función, el borrow checker administra variables en estados como “owned”, “borrowed” y “mutably borrowed”, pero esos estados no son visibles directamente para quien programa
- En
async fn, el compilador genera un struct oculto para guardar el estado suspendido, y allí puede aparecer la situación en que un campo hace borrow de otro campo - Rust no tiene sintaxis para expresar que un campo de struct está en estado borrowed, ni para expresar directamente lifetimes entre campos
- La dirección propuesta es extender el borrow checker para poder escribir borrow de campos de structs de forma directa
- Ejemplo: una sintaxis de “borrow local” como
y: &'Self::x Vec<usize> - El compilador sabría que
xestá prestado y aplicaría restricciones equivalentes a las de una variable borrowed dentro de una función
- Ejemplo: una sintaxis de “borrow local” como
- Esa sintaxis también serviría para manejar self-referential structs o casos en AST como
source: Stringjunto conast_nodes: Vec<&'Self::source str> - Un struct con borrowed fields no podría moverse, así que no implementaría
Move, e incluso se contempla un traitMoverpara tipos que puedan moverse de forma segura por sí mismos
comptime al estilo Zig y los problemas de macros en Rust
- El compilador de Rust parece, en la práctica, un sistema que maneja varios lenguajes a la vez: Rust, el lenguaje de macros de Rust y además proc macros
- La idea central es que Rust como lenguaje está bien, pero su lenguaje de macros no
- El comptime de Zig permite que el compilador ejecute partes del código en tiempo de compilación mediante un pequeño intérprete
- Funciones, parámetros,
ify loops pueden marcarse como código de compilación, mientras que el código no comptime se emite al programa real - El caso de estudio de
std printen Zig muestra cómo una format string se recibe como parámetro comptime, se parsea en un loopcomptimey luego se genera el código de salida - En Rust, la implementación de
println!()llama funciones internas comoformat_args_nl, y se sospecha que esa función está hardcodeada en el compilador - La crítica continúa diciendo que parece que ni siquiera quienes desarrollan el compilador de Rust quieren usar el lenguaje de macros de Rust
Pequeñas propuestas de sintaxis y biblioteca estándar
- Una sugerencia es corregir el problema de que
Range<T>implementeCopycuandoT: Copy - También se menciona como objetivo arreglar el problema de derive con associated types
- Issue relacionado: derive with associated types
- Ejemplo: Playground
- Se propone que la expresión
if letadmita logical AND- Forma deseada:
if let Some(x) = some_var && some_expr { } - El workaround actual es hacer matching sobre la tupla
(some_var, check_foo()), perocheck_foo()se ejecuta aunquesome_varseaNone, a diferencia del short-circuit de unifnormal - Ejemplo: Playground
- Forma deseada:
- La ergonomía de raw pointers también aparece como área de mejora
- Con referencias se puede escribir
myref.x, pero con pointers hay que escribir cosas como(*myptr).xo(*(*myptr).p).y - La postura es que el código
unsafedebería ser lo más fácil posible de leer y escribir
- Con referencias se puede escribir
- Otra propuesta es que los built-in collection types dejen de depender del allocator global y reciban explícitamente un
Allocatoren el constructor - También se dice que
asyncnecesita mejoras, pero que el tema da para otro texto aparte
Compatibilidad y viabilidad
- La mayoría de las propuestas no son compatibles con Rust actual
- Incluso agregar capabilities de seguridad introduce nuevas condiciones de fallo que rompen la compatibilidad semver de los crates, así que haría falta una nueva Rust edition
- Hace algunos años quizá se habría escrito un RFC, pero se quiere evitar pasar por un largo proceso de comentarios en GitHub para terminar con otra idea más que nunca se concrete
- Al final, la reflexión vuelve a la fantasía de hacer un fork del compilador, aunque queda la limitación práctica de que ya hay demasiados proyectos por hacer
1 comentarios
Opiniones de Hacker News
Contrario a la idea de que el proceso de RFC de Rust es un cementerio de buenas ideas, creo que el equipo central de Rust va por el camino correcto al hacer que sea difícil agregar nuevas funciones al lenguaje de programación.
Hay que evitar que la superficie del lenguaje se vuelva enorme, inconsistente e impredecible. Swift también me gustó al principio, pero terminé abandonándolo a medida que se fueron acumulando cosas como nombres de funciones redundantes como
isMultiple(of:), reglas de parseo de llaves para SwiftUI, reglas de tipos por referencia/valor y mutabilidad, y notación abreviada para argumentos de closures. Las buenas ideas abundan, así que ojalá Rust se mantenga lo más ligero posible.Por ejemplo, se pueden usar funciones que devuelven
impl Traity las estructuras pueden tener campos arbitrarios, pero no se puede poner un valor devuelto comoimpl Traiten un campo de una estructura, porque no se le puede dar un nombre al tipo.if a && bfunciona, yif let Some(x) = xtambién, peroif let Some(x) = x && bno. Me gustaría que esas cosas se corrigieran. Según la cantidad de líneas de código del compilador, Rust crecería, pero según la complejidad de aprenderlo y usarlo, estos huecos de funcionalidad vuelven al lenguaje aún más complejo.Pinno fue una gran tarea para implementarlo en la biblioteca estándar, pero para nada es una función ligera, y la carga cognitiva es enorme. Prefiero un verificador de préstamos complejo y código Rust fácil de leer antes que un compilador simple y un lenguaje difícil de usar.Incluso si el 100% de las ideas en espera fueran aceptadas y eventualmente trabajadas, se vería así por la diferencia entre la velocidad a la que entran y la velocidad a la que salen. Si quieren ir por delante de quienes escriben RFC, es mejor no escribir un RFC, sino crear una implementación lista para producción, con documentación y pruebas, que pueda integrarse limpiamente en el árbol.
No se pueden usar como const generic de otro tipo ni como longitud de un arreglo. Si solo se necesita un valor, se puede definir una función y devolverlo, así que por ahora no sirven de mucho. Si se soportaran correctamente, podrían eliminarse crates auxiliares como
generic_arrayytypenumen bibliotecas de criptografía. Aun así, estoy de acuerdo en que el equipo de Rust debe ser cuidadoso al agregar funciones.La situación de las dependencias es bastante grave, pero parece que poca gente quiere admitirlo. Un ejemplo reciente que vi es el crate cargo-watch
En esencia es una app simple que vigila cambios en archivos y vuelve a ejecutar el compilador, y su implementación no llega ni a 1,000 líneas. Pero si se vendorizan sus dependencias, crece hasta casi 4 millones de líneas de código Rust repartidas en más de 8,000 archivos. Demasiado para un simple vigilante de archivos
https://crates.io/crates/cargo-watch
C/C++ es casi el único caso entre los lenguajes de uso amplio donde no se popularizó un gestor de paquetes al estilo npm, así que la mayoría de las bibliotecas suelen ser autocontenidas o tener pocas dependencias, y opcionales.
efswes un vigilante de sistemas de archivos en C++ de 7,000 líneas sin dependencias. Las bibliotecas de un solo header del ámbito de programación de videojuegos, comostb_*,cgltf, y Dear ImGui, fueron de las experiencias más agradables que he usado. Creo que, para un nuevo gestor de paquetes, prohibir dependencias transitivas podría ser beneficioso en conjunto. Las dependencias de bibliotecas grandes podrían hacer que el usuario las instale directamente, ofrecer callbacks o convertirse en funcionalidad estándarhttps://github.com/SpartanJ/efsw
https://github.com/nothings/stb
https://github.com/jkuhlmann/cgltf
https://github.com/ocornut/imgui
Un vigilante de archivos, en particular si soporta varias plataformas, no me parece nada simple
Parte de
cargo watchtrae una biblioteca wrapper de la API Win32, que son bindings generados automáticamente para llamadas Win32, así que inevitablemente es enorme. Las bibliotecas estándar de la mayoría de los lenguajes también tienen millones de líneas, y una app solo usa una parte. Boost en C++ también es monstruosamente grande, pero los desarrolladores solo toman algunas extensiones. De hecho, es preferible tener menos dependencias grandes, mantenidas y usadas por mucha gente, que un infierno estilo npm donde un paquete comoisOddois evenpuede romper el ecosistema; Rust en general está más cerca de lo primeroLa mayoría de las dependencias de
cargo-watchparecen venir de tres requisitos directos:clap,cargo_metadataywatchexec.claparrastra muchos elementos de CLI específicos de plataforma, ycargo_metadatainevitablemente trae bastante del ecosistema deserde.watchexectiene margen de mejora: depende decommand-group, mantenido por la misma organización, y eso exige Tokio sí o sí. El problema más grande es que no se puede corregir fácilmente la sobrecarga de dependencias de otros crates. Si hay un crate atado a una versión vieja dewatchexec, a veces se puede ajustar conCargo.lock, pero normalmente es difícil y hay que rodearlo con[patch]. Sería bueno tener una forma sencilla de definir un “stand-in” para reemplazar un crate de una versión específica, pero para los gestores de paquetes existentes parece un problema de investigación importanteRust ya no es un lenguaje nuevo e interesante; está en una etapa rumbo a una adopción amplia. Que el desarrollo de funciones se desacelere es natural y saludable
Hoy, un error de diseño sería mucho más dañino que avanzar a baja velocidad. Rust es interesante no por sus funciones vistosas, sino porque es una nueva clase de lenguaje con seguridad de memoria, sin garbage collection y listo para producción. Que se adopte de verdad en lugares importantes es más interesante que mejorar el lenguaje, y eso se facilita si existe confianza en que se lo gestiona con cuidado
El operador spread, las funciones generadoras,
async, las arrow functions,leftpad, el nuevoDate, y la lista de funciones importantes no termina. JS es mucho más viejo y mucho más usado que Rust, y tiene varias implementaciones de producción, así que todas deben implementar las nuevas funciones de forma compatible. Hubo una etapa de estancamiento alrededor de ES5, pero la diferencia parece estar en que el comité de estándares de ECMAScript se puso las pilasComo los Rustaceans están obsesionados con reescribir todo en Rust, sinceramente pensé que un texto sobre reescribir Rust sería una broma metairónica
Es un poco raro que primero se quejen de la lentitud en la toma de decisiones y luego enumeren funciones cuyas razones para no estar estabilizadas no tienen mucho que ver con la toma de decisiones
Por ejemplo, las coroutines están bloqueadas porque tienen casos límite difíciles de resolver correctamente. No es que dentro del compilador haya una implementación completa que “solo haya que activar”, sino una implementación incompleta que funciona en muchos casos, pero que no se puede activar en la versión estable. En el caso de los function traits, también se decidió explícitamente no estabilizarlos en su forma actual por varias razones técnicas y por su interacción con funciones futuras. Si volviéramos al pasado, habría partes que se habrían diseñado de otra manera, pero la mayoría tiene que ver con decisiones de los primeros tiempos de Rust, cuando el equipo y los recursos eran mucho más pequeños. Se podría pensar en aprovechar que ahora se pueden tomar mejores decisiones para crear una ruptura estilo Rust 2.0, pero como el desastre de la transición de Python 2 a 3 fue tan grande, mucha gente considera mejor tolerar las asperezas. Si uno mira el newsletter semanal de Rust, las aprobaciones de RFC y las decisiones de estabilización se procesan todas las semanas. Hay casos que tardan demasiado, sí, pero los problemas de personas, coordinación y tiempo muchas veces son más difíciles de resolver que los problemas técnicos
Este artículo también debería leerse junto con la respuesta de Josh Triplett en Reddit. Uno de los ejemplos centrales del texto, Mutex, simplemente está mal
https://old.reddit.com/r/rust/comments/1fpomvp/rewriting_rus...
Corrección: el mismo comentario también está aquí
https://news.ycombinator.com/item?id=41655268
Lo primero que sentí al aprender Rust fue que parecía incluir casi cualquier función imaginable
Eso no significa que el equipo de Rust no haya rechazado cosas, pero aun así la gente quiere más funciones. Algunas están justificadas, pero otras parecen del tipo “hay que meter en el lenguaje una función que solo usará el 2% de los desarrolladores y que solo entenderá el 1%”. Un lenguaje complejo no necesita volverse más complejo. Zig es más simple y probablemente más rápido, y tiene mucho menos drama comunitario. Ojalá Zig recibiera más financiamiento
Rust no intenta meter todas las funciones posibles. Solo que puede sentirse así si no está claro qué problema resuelven cosas como GAT o TAIT. Zig puede ser un buen lenguaje moderno, pero si el objetivo es la seguridad de memoria, no es una opción
Por ejemplo, la interacción entre
Piny los futures agrega una complejidad absurda al lenguaje, y creo que parte de ella es innecesaria. Me gustaría que existiera un lenguaje parecido a Rust sinPinen absoluto. También creo que debe haber formas de simplificar el borrow checker tanto en la sintaxis como en la implementación, aunque todavía no lo he pensado en concreto. Hoy sería difícil cambiarlo sin hacer un fork del lenguaje, pero Rust no será el último lenguaje que use un borrow checker. Espero que en la próxima generación haya mucho margen para mejorar sin crear un lenguaje aún más grandeVeo drama relacionado con Rust, pero suele venir del lado que se resiste a usarlo o adoptarlo. Como el reciente alboroto de Rust for Linux. No me parece que sea algo común dentro de la comunidad, aunque quizá se me haya escapado algo. Zig es excelente, pero todavía no está listo para producción
El autor del comentario enlazado, después de analizar ampliamente las primitivas de sincronización de varios lenguajes, reescribió las primitivas de sincronización de Rust, como
MutexyRwLock, para que en cada sistema operativo usaran directamente las primitivas subyacentes del SOUsó cosas como
futexen Linux para hacerlas más rápidas, pequeñas y mejores en general, y en el proceso prácticamente escribió también el libro de programación paralela en Rust. También es útil para programación paralela que no sea en Rusthttps://www.oreilly.com/library/view/rust-atomics-and/978109...
Tampoco es que durante 7 años solo hayan estado jugando con corrutinas. Agregaron funciones asíncronas, traits que pueden contener funciones asíncronas y varias funciones necesarias para estandarizar
AsyncWriteyAsyncRead, y en nightly también hay una implementación de generadores. Se está discutiendo si asumir la complejidad de corrutinas completamente generales o quedarse en generadores. Algunas funciones, comoAsyncIterator, avanzan lento, pero también hay mucho trabajo activo. Siempre es interesante que de un lado digan que el lenguaje va demasiado lento y del otro se quejen de que va demasiado rápidoTambién hubo recientemente una gran exploración de diseño en torno a los traits de función y los sistemas de efectos, y quieren encontrar una solución para no tener que escribir varias veces las funciones para cada combinación de
async,tryyconst. El sandboxing de crates maliciosos no es un problema de nivel de lenguaje; requiere una combinación de verificadores y sandboxing en tiempo de ejecución, y los componentes de WebAssembly parecen tener más posibilidades. Dicho eso, hay mucho interés en capabilities en tiempo de compilación, como la elección del asignador o del runtime asíncrono, o la suposición de una plataforma de 64 bits, y quieren sandboxing de proc macros no para evitar acciones maliciosas, sino para lograr un caché correctoLas estructuras autorreferenciales no son un problema de sintaxis, sino uno muy difícil de manejar para el verificador de préstamos. Los préstamos parciales ya se admiten en las capturas de closures, pero la cuestión clave es cómo mantener un versionado semántico estable al exponerlos en una API pública. Una propuesta prometedora son cosas como “borrow groups” con nombre. En el lado de
comptimetambién se está trabajando en varias direcciones, y recientemente se escribió un RFC para reforzarmacro_rules. ElimpldeRangeya está en marcha, ligado a cambios incompatibles mediante una edición. La combinación deif letcon AND lógico tiene una funcionalidad inestable y está cerca de estabilizarse. También hubo varias propuestas para mejorar la sintaxis de acceso a campos de punteros, pero sigue abierta la pregunta de si ampliar más la superficie del lenguaje trae beneficios o perjuicios. Gracias a las ediciones de Rust, muchas cosas pueden cambiarse si el diseño es lo bastante convincente. Las 700 funcionalidades inestables sí son un problema real, y hace falta un gran trabajo para limpiar las que tienen pocas probabilidades de estabilizarseMacros más declarativas, delegar la lógica de crates
-sysa bibliotecas compartidas, ycfg(version)/cfg(accessible)reducirían mucho la necesidad de implementaciones por parte de los usuarios, pero el runtime seguirá existiendo. Cuanto más lo pienso, más me parece que las ACL de cackle son una buena forma extensible de rastrear operaciones en proc macros, scripts de build y código en tiempo de ejecución, y de auditar el uso del árbol de dependencias. También escuché quecargo-redpenestá evolucionando como herramienta de auditoría de llamadas, pero imagino algo de más alto nivel, como cacklehttps://github.com/cackle-rs/cackle
Entiendo que la gente se queje de que Rust ya es un lenguaje grande y no quiera que crezca más, pero mantener la implementación async a medio cocer actual no hace que el lenguaje sea más pequeño ni más simple. Solo hace que el lenguaje sea peor. También me gustaría que los préstamos parciales funcionaran aunque fuera solo dentro del mismo crate, no necesariamente como API pública. Es un problema con el que uno se topa constantemente en la programación real. También me pregunto por qué el sandboxing de crates maliciosos es imposible a nivel de lenguaje. Si una función no tiene código de terceros
unsafeen su árbol de llamadas y tampoco hace llamadas al sistema, entonces solo puede operar sobre los argumentos que ya recibió, variables locales y globales dentro del scope. Si se pudiera reforzar ese muro y usarlo como límite de seguridad, parecería posible reducir mucho el riesgo de la cadena de suministro de dependenciasLa misión de Rust fue desde el principio una tarea difícil: combinar rendimiento, seguridad y expresividad; al quedar Mozilla fuera, perdió el modo fundador, y como la mayoría del equipo central original también se fue, no sorprende que el avance se haya vuelto más lento
Personalmente, me parece mejor que ir por el camino equivocado
Si reescribiera Rust, creo que iría en la dirección de quitar funciones en vez de agregar más
Algo como lo que QBE hace frente a LLVM: ofrecer el 70% de Rust con el 10% del código. Quizá sea posible si se quitan macros y algunas funciones poco usadas
https://c9x.me/compile/
Es difícil saber qué tan posible es en realidad, ya sea con Rust o con cualquier otra cosa, antes de intentarlo