1 puntos por GN⁺ 2026-05-06 | 2 comentarios | Compartir por WhatsApp
  • Async Rust permite ejecutar código independiente del executor tanto en servidores como en microcontroladores, pero la máquina de estados que genera el compilador hace que el aumento del tamaño del binario se note especialmente en embebidos
  • Incluso un ejemplo simple como bar(), con 2 puntos de await, crea 360 líneas de MIR y los estados Unresumed, Returned, Panicked, Suspend0, Suspend1; la versión síncrona solo necesita 23 líneas
  • Si al volver a hacer poll de un future ya completado se devuelve Poll::Pending en vez de panic, se puede cumplir el contrato sin comportamiento unsafe, y en pruebas el tamaño del binario se reduce entre 2% y 5% en firmware embebido
  • Incluso async { 5 }, sin await, hoy genera una máquina de estados con 3 estados base, pero si se optimiza para devolver siempre Poll::Ready(5), el tamaño del binario embebido baja 0.2%
  • El Project Goal propuesto busca que el compilador impulse en modo release la eliminación del panic tras completarse, la eliminación de la máquina de estados en bloques async sin await, el inline de futures con un solo await y el plegado de estados idénticos

El problema del bloat a nivel de compilador en Async Rust

  • Async Rust permite ejecutar código independiente del executor tanto en servidores como en microcontroladores, pero en microcontroladores pequeños el aumento del tamaño del binario se nota especialmente
  • El blog de Rust presentó async/await como una abstracción de costo cero, pero en la práctica async sí genera bastante bloat; el mismo problema existe en desktop y servidores, aunque allí se nota menos por tener más memoria y capacidad de cómputo
  • Tras un método alternativo para evitar el bloat al escribir código async, se presentó un Project Goal para resolver el problema desde el compilador
  • Queda fuera del alcance el problema de futures más grandes de lo necesario y con demasiadas copias

Estructura del future generado

  • El código de ejemplo hace que foo() devuelva async { 5 }, y que bar() ejecute foo().await + foo().await
  • bar tiene 2 puntos de await, así que la máquina de estados necesita al menos 2 estados, pero en la práctica se generan más
  • El compilador de Rust puede volcar MIR en varias pasadas, y la pasada coroutine_resume es la última pasada de MIR específica de async
    • async aún existe en MIR, pero ya no en LLVM IR, así que la conversión de async a máquina de estados ocurre en las pasadas de MIR
  • La función bar genera 360 líneas de MIR, mientras que la versión síncrona solo usa 23 líneas
  • El CoroutineLayout que emite el compilador es, en la práctica, un conjunto de estados con forma de enum
    • Unresumed: estado inicial
    • Returned: estado completado
    • Panicked: estado posterior a un pánico
    • Suspend0: primer punto de await, guarda el future de foo
    • Suspend1: segundo punto de await, guarda el primer resultado y el segundo future de foo
  • Future::poll es una función segura, así que no debe provocar UB aunque se vuelva a llamar después de que el future ya terminó
    • Hoy, después de Suspend1, devuelve Ready y cambia el future al estado Returned
    • Si se vuelve a hacer poll en ese estado, ocurre un panic
  • El estado Panicked parece existir para impedir que se vuelva a hacer poll de ese future cuando una función async hizo panic y eso fue capturado con catch_unwind
    • Tras un panic, el future puede quedar en un estado incompleto, así que volver a hacer poll podría llevar a UB
    • Este mecanismo es muy similar al poisoning de un mutex
    • Esta interpretación del estado Panicked no está totalmente documentada; la certeza es de alrededor del 90%

¿De verdad tiene que hacer panic al hacer poll después de completarse?

  • Hoy un future en estado Returned hace panic, pero no es estrictamente necesario que sea así
    • La condición necesaria es solo que no cause UB
  • Un panic es relativamente costoso y añade una ruta con efectos secundarios que cuesta eliminar por optimización
  • Si al volver a hacer poll de un future ya completado se devuelve Poll::Pending, se puede cumplir el contrato del tipo Future sin comportamiento unsafe
  • Al modificar el compilador para probar este enfoque, se observó una reducción de 2% a 5% del tamaño del binario en firmware embebido async
  • Se propone ofrecer este comportamiento como un switch, de forma similar a overflow-checks = false para overflow de enteros
    • En builds de debug seguiría habiendo panic para exponer de inmediato el comportamiento incorrecto
    • En builds release se podrían obtener futures más pequeños
  • Con panic=abort, podría ser posible eliminar por completo el estado Panicked, aunque su impacto requiere más revisión

Siempre se genera una máquina de estados, incluso sin await

  • foo() solo devuelve async { 5 }, así que la forma óptima manual sería un future sin estado que siempre devuelva Poll::Ready(5)
  • Sin embargo, en el MIR que genera el compilador siguen existiendo los 3 estados base: Unresumed, Returned, Panicked
    • Al hacer poll, se revisa el discriminante del estado actual y se bifurca en consecuencia
    • Si se vuelve a hacer poll tras completarse, hay un panic con el assert `async fn` resumed after completion
  • En este caso se puede optimizar para no crear ninguna máquina de estados y devolver siempre Poll::Ready(5)
  • Al aplicar esto experimentalmente en el compilador, el tamaño del binario embebido se redujo 0.2%
    • El ahorro no es grande, pero la optimización es simple y podría valer la pena
  • Esta optimización cambia un poco el comportamiento, pero solo afecta a executors que no respetan el contrato
    • Hoy el compilador hace panic en polls posteriores
    • Tras la optimización, el future siempre devolvería Ready

Solo con LLVM no alcanza

  • Aunque la salida MIR sea ineficiente, a veces LLVM puede limpiar todo, pero bajo condiciones limitadas
    • El future debe ser lo suficientemente simple
    • Hay que usar opt-level=3
  • Si el future se vuelve más complejo, LLVM ya no logra eliminarlo, y en código async idiomático de Rust los futures tienden a anidarse profundamente, así que la complejidad crece rápido
  • En entornos donde a menudo se optimiza por tamaño, como embebidos o wasm, LLVM no logra optimizar todo esto por sí solo
  • Ejemplo en Godbolt: https://godbolt.org/z/58ahb3nne
    • En el ensamblador generado, LLVM sabe que foo devuelve 5, pero no logra optimizar la respuesta de bar a 10
    • La llamada a la función poll de foo sigue presente
    • La razón son rutas potenciales de panic que el compilador no puede descartar completamente
    • LLVM no sabe que foo en realidad solo se llama una vez y que no va a hacer panic
  • Si se comenta la rama de panic en el IR, la optimización mejora: https://godbolt.org/z/38KqjsY8E
  • En vez de esperar una optimización posterior de LLVM, el compilador debería darle una mejor entrada a LLVM

El inline de futures no está funcionando bien

  • El inline es importante porque habilita pasadas de optimización posteriores, pero los futures generados por Rust hoy no se inlinean en etapas tempranas
  • Una vez que cada future obtiene su implementación, LLVM y el linker sí tienen oportunidades de inline, pero por los problemas anteriores ese momento ya llega demasiado tarde
  • La oportunidad de inline más directa es una forma donde bar() solo hace foo(blah).await
    • Es un patrón que aparece seguido al construir abstracciones con traits
    • Hoy el compilador crea una máquina de estados para bar y dentro de ella llama a la máquina de estados de foo
    • De manera más eficiente, bar podría ser directamente el future de foo
  • Cuando hay preamble y postamble, la cosa es más compleja
    • Ejemplo: bar(input) crea blah con input > 10, luego hace foo(blah).await y finalmente aplica * 2 al resultado
    • Es algo común al transformar funciones async a otras firmas, especialmente en implementaciones de traits
  • Incluso en esta forma, bar no necesita tener estado async propio
    • No hay datos preservados más allá del único punto de await excepto el valor capturado por foo
    • Aun así, bar no puede convertirse simplemente en foo, sino que la mayor parte del estado puede delegarse a foo
  • En una implementación manual, BarFut podría tener los estados Unresumed { input } e Inlined { foo: FooFut }
    • En el primer poll ejecutaría el preamble para crear foo(blah) y cambiar al estado Inlined
    • Después aplicaría el postamble al resultado de foo.poll(cx)
  • Si el código pudiera ejecutarse por adelantado hasta el primer punto de await, también podría eliminarse el estado Unresumed, pero no puede hacerse porque está garantizado que un future no hace nada antes de recibir poll
  • Si se pudieran consultar propiedades del future mientras está siendo polleado, sería posible aplicar optimizaciones de inline adicionales
    • Por ejemplo, si se supiera que un future siempre devuelve ready en su primer poll, el future llamador no necesitaría crear un estado para ese punto de await
    • Si este tipo de optimización se aplicara recursivamente, muchos futures podrían plegarse en máquinas de estados mucho más simples
  • En la estructura actual de rustc, cada bloque async parece transformarse por separado y luego no se conserva la información relacionada, así que este tipo de consulta no parece posible
  • El inline de futures aún no se ha probado, pero se espera que ayude bastante tanto en tamaño de binario como en rendimiento

Plegado de estados idénticos

  • Cada punto de await dentro de un bloque async añade un estado extra a la máquina de estados
  • Un código como el siguiente es natural, pero como ambas ramas hacen await de la misma función async, se generan 2 estados idénticos
    • CommandId::A => send_response(123).await
    • CommandId::B => send_response(456).await
  • En este caso, el CoroutineLayout crea _s0 y _s1, ambos almacenando el mismo tipo de coroutine de send_response, además de los estados Suspend0 y Suspend1
  • El MIR de esta función tiene 456 líneas, y muchos bloques básicos son en la práctica duplicados
  • Si antes se refactoriza manualmente el código para calcular primero solo el valor de respuesta y luego hacer una sola vez send_response(response).await, desaparecen los estados duplicados
    • CommandId::A produce 123
    • CommandId::B produce 456
    • Después se hace send_response(response).await
  • Tras el refactor, en CoroutineLayout queda almacenado un solo future y solo queda el estado Suspend0
  • La longitud total del MIR baja a 302 líneas y desaparece la duplicación
  • Por eso parece útil una pasada de optimización que detecte rutas de código y estados equivalentes para plegarlos en uno solo
    • Esta optimización probablemente combine bien con una pasada de inline de futures

Enlaces de experimentos y benchmarks adicionales

Solicitud de apoyo para el Project Goal

2 comentarios

 
GN⁺ 2026-05-06
Comentarios de Hacker News
  • Estoy de acuerdo en que el título es un poco exagerado, pero el texto está bien escrito y transmite bien la idea principal
    Aún no tengo tanta experiencia con Rust async como para tener opiniones muy firmes, pero sí hubo varias cosas que me llamaron la atención
    Lo bueno es que puedes tener un runtime explícito. En vez de contaminar todo el proyecto con async, puedes dejar lo síncrono como base y usar el runtime solo en los “límites” de entrada/salida
    En un proyecto en el que estoy trabajando, este enfoque encajó bien, y se ve bastante parecido a la estrategia que Zig adopta para el código de entrada/salida. En este caso, también resolvió en gran parte el problema del color de las funciones, y como necesitábamos separar estrictamente el código de entrada/salida del código orientado a CPU, un runtime explícito de I/O se sentía natural
    Lo malo es que todo el ecosistema parece depender demasiado de tokio. Es parecido a si en Java el GC fuera opcional, pero en la práctica todos usaran el mismo runtime de GC de terceros, y cualquier librería que importaras te lo impusiera. Ese tipo de dependencia central no es saludable

    • Dependiendo del contexto, puede parecer que todo el ecosistema depende de tokio, pero si miras Rust embebido resulta más comprensible
      Los requisitos de un runtime async en procesadores de estación de trabajo y en entornos como un RP2040 son muy distintos. Aun así, como puedes cambiar el backend, incluso cuando escribes código async de entrada/salida para un microcontrolador ARM M0 pequeño, si usas embassy, que es un runtime orientado a embebidos, el código se ve casi igual al que usarías en otros entornos
      Como se usan los mismos trait e interfaces, puedes preocuparte menos por los detalles del runtime. Comparado con usar un RTOS pequeño o construir tú mismo el entorno async, está bastante bien
      Lo que aprendes usando código async con embassy también lo puedes llevar a otras áreas
    • Me pregunto cuál sería la alternativa. Estoy satisfecho usando tokio, pero también me parece bien que otros usen otros ejecutores como smol, async-std o glommio
      Aunque tokio no sea parte de la biblioteca estándar, está bien mantenido, así que la situación actual me parece aceptable. De hecho, me preocupa que si entrara a la biblioteca estándar sería más difícil usar otros ejecutores, y también sería más difícil portar la biblioteca estándar a otras plataformas
      Claro, puede que esta preocupación no tenga fundamento
    • Es interesante que menciones Java, porque Java también ha pasado históricamente por problemas parecidos
      El logging hoy más o menos se ordenó con slf4j, pero todavía hay librerías que usan otras cosas, y las utilidades comunes empezaron con Apache Commons y ahora muchas usan Guava
      JSON se ordenó en cierta medida con Jackson, pero Gson y Simple-json siguen siendo comunes, y las anotaciones de nulabilidad tampoco lograron oficializarse: pasaron de distribuciones no oficiales del fallido JSR-305, luego por checker framework, y más recientemente hacia JSpecify
      Estos elementos básicos debería proveerlos el lenguaje para evitar la fragmentación y la proliferación de bibliotecas estándar de facto
    • Hay muchas áreas donde puedes usar Rust con async sin depender de tokio. En realidad, lo que sí parece totalmente atado a tokio es más bien la parte web/servidor
      Escribir librerías independientes del ejecutor no es especialmente difícil, pero sí exige atención constante, y no es algo que toda la comunidad mantenga siempre
  • Excelente artículo. Me encantan este tipo de análisis profundos de optimización, y ojalá el proyecto también logre bien sus objetivos
    A veces he sentido que el compilador no invierte tanto esfuerzo en optimizar los casos “triviales”
    Aun así, el título es demasiado dramático para lo que realmente dice el contenido. Igual habría hecho clic si se llamara “Async Rust Optimizations the Compiler Still Misses”

    • Elegí el título así simplemente porque es cierto. Desde que async llegó, por ahí de 2019, no han cambiado muchas cosas de fondo
      Ahora ya se puede usar async en traits y closures, pero eso es una actualización del sistema de tipos, no un cambio en la maquinaria de async en sí. Waker también se volvió un poco más fácil de manejar, pero eso está más cerca de una mejora de std/core
      Según entiendo, la gente que llevó async Rust a tierra terminó bastante quemada y bajó mucho su actividad, y casi nadie tomó realmente el relevo. Aun así, me alegra bastante ver que gente de Google dejó abierto un PR para optimizar la disposición en memoria de las variables capturadas
      Mis colegas y yo usamos mucho async, así que quizá tengamos que hacerlo nosotros mismos, o al menos empezar. Se parece más al tipo de “gratis” de “tener un cachorro es gratis”
      Así que sí, el título es un poco bait, pero aun así no pienso retirarlo
    • Estoy de acuerdo en que el título está demasiado exagerado
      El autor parece obsesionado con el overhead de funciones triviales. Le molestan los costos extra de los estados “panic” y “returned”, pero eso no es un gran problema
      La mayoría de los bloques async útiles son lo bastante grandes como para que el overhead de los casos de error quede diluido
      Sobre la falta de inlining, puede que tenga razón. Pero lo que normalmente limita una gran cantidad de actividades es más bien el espacio de estado que requiere cada una
  • En general, async me parece una idea menos madura. El código normal ya era asíncrono
    Si tienes que esperar una tarea async, el hilo se duerme hasta que esté lista, y el kernel abstrae eso. Pero como a la gente no le gustaba estructurar el código con hilos lógicos, se añadió un sistema de callbacks para eventos, y después nos dimos cuenta de que los callbacks son difíciles de razonar y que el control secuencial es mejor
    Así que yo diría que los hilos eran el modelo correcto de programación
    Ahora los runtimes de lenguaje prefieren los “green threads” por portabilidad y rendimiento, pero la mayoría de los lenguajes no los ofrecen bien. En lugar de eso aparecen problemas como el color async/non-async, scheduling, prioridades y ausencia de preempción. Es un modelo de scheduling y de procesos peor que el de los años 70

    • Eso de que “el código normal ya era async, y cuando espera el hilo se duerme y el kernel lo abstrae” no es correcto
      El código async también suele escribirse de maneras que no maximizan toda la concurrencia expresable. Por ejemplo, en vez de “ejecuta N tareas de entrada/salida al mismo tiempo”, se escribe algo como “para cada tarea X, await process(x)”
      Pero en el mundo de los hilos este problema de concurrencia es todavía peor. Los hilos son inherentemente demasiado pesados, así que expresar concurrencia de forma eficiente es difícil, y no hay manera de optimizarlos en esa dirección
      Esta no es una lección nueva. Desde hace mucho se sabe que un ejecutor con work stealing ofrece latencias mucho menores y un P99 más consistente que los hilos tradicionales. Por eso Apple creó GCD a inicios de los 2000
      Los hilos no le dan al scheduler del kernel la información más rica que necesitaría para entender la carga de trabajo, y los hilos del kernel son un mecanismo demasiado pesado para conseguir concurrencia fina. Cuando no es cómputo puro sino entrada/salida o cargas mixtas, es aún peor
      No todos los programas necesitan ese nivel de rendimiento, pero con el mismo esfuerzo es mucho más fácil alcanzar una vara de rendimiento más alta, y de hecho puedes obtener latencia y throughput que el enfoque tradicional difícilmente alcanza
      Que async va en la dirección correcta también se nota en io_uring. El enfoque de I/O de alto rendimiento del kernel con io_uring es completamente distinto al threading y a las system calls tradicionales, y el manejo de finalización se parece mucho más a la concurrencia async. Eso sí, solo con async/await es más difícil aprovecharlo del todo porque no hay suficientes “colores” para expresar las relaciones entre tareas async
    • En el momento en que entran el kernel y el scheduler del SO, puedes volverte 3 o 4 órdenes de magnitud más lento de lo que sería posible
      La última vez que trabajé con código de corutinas/scheduling, crear un hilo que terminara de inmediato y hacerle join tomaba unos 200µs, mientras que crear, planificar y esperar un green thread propio tomaba unos 400ns
      No hace falta esperar 10 años a que alguien diseñe otro framework async absurdamente complejo. En cualquier lenguaje de sistemas puedes hacer tú mismo green threads/corutinas con stack con unas 20 líneas de ensamblador
    • Que “los hilos sean el modelo correcto de programación” depende de qué estés haciendo. Para trabajo orientado a cómputo, los hilos encajan bien; para trabajo orientado a ancho de banda, async encaja mejor
      Optimizar código orientado a ancho de banda es un problema de diseño del scheduling. En el modelo clásico multihilo, el control del scheduling es limitado, mientras que en el modelo async puedes controlarlo casi por completo
      Un schedule async bien optimizado puede ser muchísimo más rápido que una arquitectura multihilo equivalente para la misma carga orientada a ancho de banda; ni siquiera hay comparación
      Hoy gran parte del código de alto rendimiento está orientado a ancho de banda, y async existe para facilitar la optimización de ese tipo de cargas
    • Yo diría que los callbacks son más fáciles de razonar
      Cuando pruebas procesamiento concurrente y quieres verificar que maneja bien las race conditions, con callbacks es mucho más fácil porque puedes controlar el scheduling. Como cada callback representa una unidad separada, puedes ver qué eventos se pueden reordenar y revisar distintos órdenes más fácilmente
      En cambio, con hilos es fácil ignorar el orden y dejar de pensar en cuándo la complejidad que ocurre en otros hilos puede afectar al hilo actual. No es tanto simplicidad como simplificación
      Además, es difícil probar cambiando realmente los escenarios concurrentes, a menos que metas barreras artificiales para detener hilos o reemplaces la entrada/salida con stubs y pases mocks con callbacks para controlar el orden
      El problema de los callbacks es que el stack trace capturado no es el stack lógico de llamadas. Si no usas alguna librería/runtime que haya trabajado en hacerlo significativo, necesitas una buena definición de errores
      Claro, también puedes mezclar ambos paradigmas y terminar con lo peor de los dos
    • Los hilos no son mejores ni peores que async+callbacks: son otro modelo. Hay problemas que encajan bien con hilos y otros que se expresan mucho mejor con async
  • Si el objetivo principal de Rust es la seguridad, no entiendo por qué existe panic. Debería poder demostrarse que no hay ninguna ruta posible hacia un panic en el código
    Estuve viendo esto toda la semana, y es muy difícil crear un programa que garantice que jamás hará panic. Según entiendo, el panic handler ocupa unos 300KB, y la única forma de excluirlo es que en tiempo de compilación no exista ninguna ruta capaz de panic en el código. Tener que revisar el binario después de compilar para ver si el panic handler quedó incluido se siente como un hack
    Se puede prohibir unwrap y otras operaciones que hacen panic con lints, pero si existiera un subconjunto no-panic de Rust, muchos de los problemas tratados en este artículo desaparecerían
    Es frustrante tratar con un lenguaje donde hay demasiadas operaciones que, en teoría, pueden panic, incluso en situaciones que en la práctica no ocurrirían salvo al nivel de un bit flip. Pasa igual cuando intentas demostrar que un arreglo no está vacío o cuando lidias con async
    Al final terminas metiendo un montón de manejo de errores para situaciones que jamás van a pasar, o usando estructuras raras como el patrón de lista no vacía con un primer campo y el resto por separado. Y aun esa estructura añade su propia hinchazón

    • En Rust-for-Linux están abordando este problema con cosas como operaciones de memoria que pueden fallar. Para ellos es una capacidad necesaria
      También avanza lentamente el trabajo para ampliar los usos basados en pruebas, incluyendo pruebas de que un arreglo no está vacío y cosas así
    • panic es bastante importante para la usabilidad y la seguridad
      Si no existiera panic y hubiera que seguir ejecutando en toda situación, entonces en cualquier lugar donde revisas invariantes para recuperarte de algo como corrupción de memoria que rompe invariantes, tendrías que meter mucho manejo de errores
      Eso es exactamente el mismo tipo de problema que te preocupa: enormes cantidades de manejo de errores para situaciones que casi nunca ocurren
    • El objetivo de Rust es la seguridad de memoria. panic es completamente seguro desde el punto de vista de la seguridad de memoria
    • Ni siquiera el sistema operativo que ejecuta tu programa es perfecto
      Cansa un poco esa actitud de esperar que las herramientas vuelvan imposible todo fallo sin que uno mismo quiera involucrarse en hacer nada. Se quiere una API fácil, y si no es suficientemente fácil, entonces se quiere “programar” contenedores de Kubernetes en YAML, y si eso tampoco es suficientemente fácil, se quiere un servicio de hosting a clics de GCP o Amazon
      Al final se parece menos a querer programar y más a querer consumir aplicaciones que no fallen, y ese estilo de vida solo existe sobre una relación de simbiosis con la gente que sí construye cosas
  • Este tipo de discusión fea pero necesaria también ha estado presente por un tiempo en C++
    Desde que async llegó a Rust no me gustó su naturaleza contagiosa
    Quiero que a Rust le vaya bien, y si aparece más gente así, el futuro de Rust puede verse más prometedor

  • Hace poco empecé a trabajar con async en Rust, y el principal problema que estoy sufriendo ahora mismo es la duplicación de código
    Cada función que quiero que soporte tanto una API asíncrona como una bloqueante tengo que escribirla dos veces. Me gustaría que existiera maybe-async
    Traté de esquivarlo mirando crates como maybe-async y bisync, pero todos tenían problemas o restricciones fuertes

    • Está avanzando trabajo en genéricos de palabra clave, para poder hacer funciones genéricas respecto a keywords como async o const
      Hoy, la mejor opción para escribir código que quiera vivir tanto en el mundo síncrono como en el asíncrono es sans-io. Thomas Eizinger, de Fireguard, escribió un buen artículo sobre este patrón[1]
      Este patrón no solo resuelve de forma elegante el problema sync/async, también facilita las pruebas y abre la puerta a técnicas como DST[2]
      Yo también escribí un artículo sobre esto[3], donde remarco que el problema va más allá de async vs sync e incluye una cuestión más amplia entre distintos ejecutores
      0: https://github.com/rust-lang/effects-initiative
      1: https://www.firezone.dev/blog/sans-io
      2: https://notes.eatonphil.com/2024-08-20-deterministic-simulat...
      3: https://hugotunius.se/2024/03/08/on-async-rust.html
    • Depende mucho de lo que estés haciendo en la práctica, pero si es lo bastante simple quizá podrías hacer un macro que intercambie tipos y await
    • Es el clásico problema del color de las funciones. https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
    • Desde mi punto de vista, una función async ya es maybe-async
      La diferencia entre fn -> void y fn -> Future es que la primera termina de ejecutarse inmediatamente hasta el final, mientras que la segunda puede terminar más tarde
      Si quieres ejecutar una función async de forma bloqueante, basta con usar un ejecutor bloqueante
  • Lo que me gusta de este artículo es que también me permitió ver los objetivos de Rust para 2026
    En el equipo usamos Rust, pero no hemos necesitado meternos tan a fondo para hacer lo que necesitamos. Aun así, es entretenido ver cómo un lenguaje con tanto feedback de la comunidad va desarrollándose desde la base
    No sentí algo así con C++, y en otras áreas tampoco sé bien cómo funciona
    Lo que sí me deja una sensación rara es que cada objetivo parece necesitar financiamiento específico, así que se siente un poco como Kickstarter. Me pregunto si de verdad este es el mejor modelo que se ha encontrado hasta ahora

    • El término “objetivo de proyecto” da una impresión bastante engañosa frente a lo que realmente significa
      Un objetivo de proyecto es un sistema donde una persona o un grupo pequeño expresa que quiere trabajar en algo, y pide a voluntarios del proyecto Rust tiempo de apoyo continuo, como revisión de código o respuesta a preguntas
      Eso no significa que el proyecto Rust como tal haya fijado ese objetivo ni que necesariamente lo respalde
      Así que no sería correcto verlo como la hoja de ruta oficial de Rust; es más preciso verlo como “hay contribuyentes interesados en trabajar en esta área”
    • Parece haber cierto consenso dentro del comité ISO de C++ en que el proceso de evolución de ese lenguaje está roto hasta cierto punto. Principalmente por su escala y por cómo está organizado
      Cuando una tecnología se establece comercialmente, por desgracia parece que las cosas tienden a ir por ahí. Tampoco es fácil culpar a grandes patrocinadores por financiar solo las partes que les interesan
      Tengo entendido que una parte importante del financiamiento de TweedeGolf viene del gobierno neerlandés
    • En el trabajo open source parece haber dos clases generales: desarrollo de funcionalidades y mantenimiento
      Las nuevas funcionalidades se pueden “vender”. Cuestan dinero construirlas, pero resuelven problemas reales, y si el costo del problema es mayor que el costo de desarrollar la funcionalidad, las empresas por lo general están dispuestas a pagar
      El mantenimiento es más difícil, pero ahora también existen fondos para maintainers. Un ejemplo es el fondo de RustNL: https://rustnl.org/maintainers/
      Estos fondos apuntan a trabajo más amplio y sostenido, y se sostienen con pequeños aportes de varias organizaciones
      No sé si sea el mejor modelo, pero al menos parece funcionar hasta cierto punto
  • Si lees la documentación de Rust Async y Tokio, explican bien por qué no debes meter partes intensivas en CPU dentro del stack async, cómo usar eficientemente herramientas básicas como std::sync::Mutex dentro de bloques async, y cómo unir código síncrono con código async
    Mucho código no sigue estas guías porque no le importa la eficiencia o no la necesita. Pero hay muchos proyectos que sí valoran rendimiento y eficiencia, y una vez que el código corre en producción te das cuenta de las trampas. ScyllaDB es un ejemplo
    Los LLM tampoco ayudan. Generan todo como async hasta main, usan mal las herramientas básicas y no diseñan correctamente el sistema

  • El plegado de estado duplicado, es decir, el patrón de sacar el match fuera de la rama con await como en el ejemplo de process_command, es probablemente la forma más fácil que cualquiera puede aplicar hoy al código async existente
    No requiere trabajo del compilador, solo refactorización

    • Como mínimo, haría falta un lint personalizado que encontrara dónde se puede aplicar. Eso ya está bastante cerca de trabajo de compilador
  • Sobre la parte de que “los Future no se inlinean fácilmente”, en un lenguaje de programación que hice escribí un pase personalizado que inlinea llamadas a funciones async dentro de funciones async
    En general funciona bien y elimina parte del boilerplate, pero el tamaño del binario resultante crece bastante
    Técnicamente, Rust también podría hacer lo mismo

 
GN⁺ 2026-05-06
Comentarios de Lobste.rs
  • Fue un texto mucho más constructivo de lo que esperaba viendo solo el título

    • Creo que es básicamente cierto. Ya pasaron 7 años desde el lanzamiento del MVP, pero casi no hubo avances ni en el diseño del lenguaje ni en la implementación del compilador, y como las personas que impulsaron principalmente el MVP redujeron su participación en el proyecto más o menos en esa misma época, el relevo después de eso quedó estancado.
      Ojalá quien quiera trabajar en esto reciba el apoyo que necesita
  • I want to work on this in the compiler and as such have submitted it as a Project Goal

    Stop generating statemachines that don’t have to be there
    Make the compiler’s job easier by removing panic paths and branches
    Make statemachines smaller

    Me alegra ver que se esté abordando este problema. Ya había leído varias veces que ahora mismo rustc le está pasando demasiado código a LLVM y espera que el optimizador resuelva todo, y este texto en particular además está pidiendo financiamiento para ese trabajo

  • Dios mío, yo era un idiota
    Siempre pensé que async era intrínsecamente “inflado”, porque de una forma u otra necesita un runtime, seguimiento de tareas y polling para verificar la finalización. Ese overhead no es cero, al fin y al cabo
    Lo que se quería decir aquí con “abstracción de costo cero” era sobre la funcionalidad del lenguaje, y yo lo veía como algo aparte del runtime agregado
    Ni siquiera se me había ocurrido mirar qué está emitiendo rustc antes de pasárselo a LLVM

  • Para quienes no están familiarizados con async Rust:

    It's amazing how we can write executor agnostic code that can run concurrently on huge servers and tiny microcontrollers.

    Esto es totalmente cierto. Incluso un árbol anidado de llamadas async, después de una optimización máxima, se solidifica internamente en una sola estructura con una máquina de estados dentro. Es una forma realmente ingeniosa de hacerlo

  • ¿En un build de release, llegar a este caso genera una especie de deadlock? ¿O también podría producir fugas porque hay tareas esperando trabajo que siempre queda en Pending?

    • Sí. Esos futures quedan en un estado atascado y nunca terminan. Pero ese estado solo puede alcanzarse en código async de bajo nivel que ya tiene bugs, y es muy probable que el código que no logra rastrear correctamente un future completado ya esté produciendo fugas y deadlocks
      No puedes hacer polling incorrecto con .await
  • Se me ocurren algunas cosas:

    1. Este texto parece argumentar que habría que sacar más lógica de optimización fuera de LLVM y moverla a la capa MIR. Por ejemplo, entiendo por qué hacer inline de funciones async sería más fácil en MIR que en LLVM. Si eso se logró en MIR para async, me pregunto si esa lógica podría generalizarse también para funciones síncronas y eliminar algunos de los passes de optimización de LLVM. Sé que sería un trabajo grande, y no es tanto una pregunta práctica como de dirección. Cuando un compilador de frontend/middle-end alcanza cierto nivel de complejidad, quizá sea mejor trasladar bastante de la optimización genérica de LLVM a otro lugar
    2. Sigo sin estar convencido con panic=unwind. Fuera de algunos test harnesses, casi nunca he visto ventajas frente a panic=abort que compensen su costo. Incluso en un test harness, en Linux parece que podría aplicarse una elección parecida usando clone de una forma rebuscada para hacer wait al hilo de ejecución en vez de pthread_join. Puede que yo esté equivocado en esto
  • ¿El enlace también se le cayó a alguien más hace un momento?
    Edit: la entrada del blog se ve como medio segundo y luego te manda a una página 404
    Edit 2: entré a la lista de posts del blog y estuve picándole a varias cosas, y hasta abriendo ese post desde la lista igual me manda a una página 404. ¿Cómo se puede arruinar así un blog que es una página estática, o al menos debería serlo?

    • El tono se siente un poco innecesariamente grosero y agresivo. Los sitios web también pueden tener bugs; reportarlo es útil, pero este comentario suena algo mezquino
      Como referencia, creo que seguí los mismos pasos de reproducción y a mí no me salió ningún 404. Lo probé en celular y escritorio, con JavaScript activado y desactivado. Así que da la impresión de que lo que te pasó pudo haber sido más complejo de lo que parecía