Async/await no es lo mismo en Swift que en Trio: nueve ejes de diseño divergentes
Un paper de la Universidad de Brown ejecuta el mismo programa en siete runtimes y obtiene cuatro salidas distintas. La divergencia no está en el rendimiento, sino en nueve decisiones semánticas.

Un paper de la Universidad de Brown ha hecho ejecutar el mismo programa a siete runtimes modernos de async/await y ha recogido cuatro salidas distintas. Los autores no discuten rendimiento ni sintaxis, sino semántica observable: bajo las mismas dos palabras, cada lenguaje decide de otra forma cuándo arranca una tarea, cuánto vive y cómo muere.
El ejemplo cabe en un puñado de líneas. Una función escribe una entrada de log: imprime A, espera dos segundos simulando una escritura lenta e imprime B. Otra la lanza en segundo plano sin esperarla. El main llama a esa segunda función, duerme un segundo e imprime C. En Swift sale AC; en Trio, la biblioteca de concurrencia para Python, sale ABC. Con tres variaciones del mismo programa, según los autores no hay dos de los siete runtimes que coincidan en el resultado.
La causa es que async/await acumula quince años de propuestas y cada implementación resolvió el problema a su manera. Python, Rust y Swift comparten la idea de partida —escribir código concurrente con aspecto de código secuencial, la asincronía de línea recta que sustituye a bucles de eventos y callbacks— pero no las decisiones concretas.
Nueve ejes, agrupados por la vida de la tarea
El paper ordena esas decisiones en nueve dimensiones repartidas en tres bloques. En el nacimiento de la tarea están la eagerness (¿la llamada devuelve una tarea ya en marcha o un objeto inerte hasta que se espera?) y las garantías de suspensión. En su final, el extent (¿la tarea sobrevive a la función que la creó?), la fuerza de la referencia que guarda el runtime, el mecanismo de destrucción y el destino de las excepciones en tareas no esperadas. Y la cancelación: si la tarea se entera de que la cancelan, en qué dirección se propaga y si persiste.
El caso de Swift y Trio enseña cómo dos decisiones producen dos salidas. Ambos eligen extent dinámico: la tarea no puede vivir más que la función que la lanzó. Pero Swift destruye cancelando y Trio destruye esperando a que write_to_log termine. De ahí AC frente a ABC.
Los autores tradujeron el espacio de diseño a una semántica formal sobre un cálculo reducido de programas asíncronos, que permite trazar la ejecución paso a paso y señalar qué regla conduce a cada resultado.
Queda una advertencia práctica: async/await no describe un comportamiento único, y las suposiciones que aguantan en un lenguaje se rompen al portarlas a otro. Saber qué eligió el tuyo decide si esa tarea lanzada en segundo plano sigue viva cuando sales de la función.


