La pesadilla de mi existencia: soportar código asíncrono y síncrono en Rust
Soportar código síncrono y asíncrono en Rust expone una tensión más profunda en la programación moderna: una vez que una biblioteca adopta I/O async, su “color” tiende a propagarse por la API, complicando el uso para quienes quieren interfaces simples y bloqueantes. Los comentaristas contrastan el ecosistema de Rust —donde múltiples runtimes, la falta de traits async estandarizados y las restricciones de feature flags hacen doloroso el soporte dual sync/async— con lenguajes como Go, JavaScript, Python y Zig, cada uno con distintos compromisos entre ergonomía, rendimiento y complejidad de runtime. Las soluciones propuestas van desde crates separados sync/async y wrappers `block_on` hasta diseños sans-I/O y sistemas de efectos, pero ninguna elimina por completo la carga de mantenimiento y diseño para los autores de bibliotecas.
Dolor en bibliotecas Rust: soportar sync y async
- Problema central: las bibliotecas quieren ofrecer APIs tanto síncronas como asíncronas sin duplicación de código, explosión de flags de características ni obligar a los usuarios síncronos a adoptar un runtime asíncrono.
- Las reglas de features de Cargo (“las features deben ser aditivas”) hacen incómodas las configuraciones mutuamente excluyentes de sync/async.
- Mantener dos crates (por ejemplo,
-syncy-async) o dos rutas de código paralelas se probó y resultó engorroso. - A los usuarios tampoco les gusta que los obliguen a usar async porque una dependencia lo eligió.
Enfoques propuestos
- Usar
block_onen un hilo dedicado para exponer una fachada síncrona sobre un núcleo asíncrono; muchos lo consideran un compromiso pragmático. - Mantener solo una API async y sugerir que los usuarios síncronos bloqueen sobre ella, aunque eso siga “teñiendo” al llamador.
- Crates o módulos separados para sync y async, con algo de discusión menor sobre convenciones de nombres.
- Estilo Sans-IO: exponer tipos puros de petición/respuesta y dejar que los llamadores elijan clientes HTTP síncronos o asíncronos.
- Macros / codegen (como
unasyncde Python o ideas parecidas a “multisync” de Nim) para generar automáticamente variantes sync y async.
Async vs sync: debate conceptual
- Algunos ven async como algo “hermoso” y la forma más natural de expresar máquinas de estado complejas e I/O de alta concurrencia.
- Otros encuentran futures y máquinas de estado menos naturales que los hilos acotados para muchas cargas de trabajo.
- Varios sostienen que los beneficios de async aparecen solo cuando muchas tareas se ejecutan concurrentemente; para código puramente secuencial añade carga mental.
- Hay desacuerdo sobre si async evita los bugs de concurrencia; otros señalan que las condiciones de carrera pueden seguir apareciendo cuando se inserta
awaiten código que antes era lineal.
Comparaciones entre lenguajes
- JS: async se integra con suavidad porque el I/O siempre fue basado en callbacks y de un solo hilo; aun así, ocultar async bajo sync sigue siendo difícil.
- Python: async abre nuevos patrones, pero
asyncioes criticado por ser pesado y, a veces, más lento que los hilos simples. - Go: elogiado por un único modelo de concurrencia “verde”; debate sobre si realmente evita el color de funciones o solo estandariza un solo “color”.
- Haskell: los hilos verdes y
asyncse ven como más agradables ergonómicamente; la inmutabilidad ayuda a razonar sobre la concurrencia.
Coloración de funciones y runtimes
- La “coloración de funciones” se cita ampliamente como el dolor de fondo: las anotaciones async se propagan de forma viral por las pilas de llamadas y entre módulos.
- La falta de un runtime integrado en Rust y la fragmentación entre Tokio y otros runtimes agravan la fricción (traits async, acoplamiento al runtime, semántica de cancelación).
- Algunos esperan soluciones futuras (generics por palabra clave, sistemas de efectos, diseños async-generic) para reducir la duplicación y hacer el código agnóstico al runtime.
Inmutabilidad, concurrencia y bugs
- Un subhilo argumenta que la inmutabilidad combina bien con async/hilos al eliminar data races; otros responden que la inmutabilidad se sobrevalora y desplaza la complejidad hacia los mecanismos de actualización.
- No hay consenso; los participantes aportan anécdotas tanto a favor como en contra de la inmutabilidad como “bala de plata” para la corrección concurrente.