La programación asíncrona en Rust está diseñada para optimizar tareas de tipo IO-bound, como la lectura de archivos, peticiones de red o consultas a bases de datos. A diferencia de la programación paralela tradicional, que busca ejecutar múltiples hilos simultáneamente en diferentes núcleos de la CPU (CPU-bound), la asincronía busca que un solo hilo pueda realizar otras tareas mientras espera que una operación de entrada/salida se complete.
El núcleo de este modelo es el trait Future. Cuando defines una función con async fn, esta no ejecuta su cuerpo inmediatamente al ser llamada; en su lugar, retorna un objeto que implementa el trait Future. Este objeto es una representación de un trabajo que se completará en el futuro. El código dentro de un async fn es “perezoso” (lazy): no se ejecuta hasta que el Future es “polled” (consultado) por un motor de ejecución o executor.
Para que el trabajo se realice, Rust requiere un runtime (como tokio o async-std), ya que el lenguaje no incluye uno por defecto. El runtime es el encargado de gestionar los hilos y decidir cuándo “despertar” un Future basándose en la disponibilidad de datos. El operador .await es el mecanismo de suspensión: permite que la ejecución de la función actual se detenga y devuelva el control al runtime sin bloquear el hilo de ejecución, permitiendo que este último use el hilo para procesar otras tareas pendientes hasta que la operación original esté lista.
Este modelo se conoce como “zero-cost async” porque el compilador de Rust transforma cada bloque async o función async en una máquina de estados altamente optimizada. Esto evita la sobrecarga de memoria y el costo de contexto de los hilos del sistema operativo, ya que la transición entre estados ocurre mediante llamadas a funciones eficientes en lugar de cambios de contexto de kernel.
use tokio::time::{sleep, Duration}; // Se requiere un runtime como tokio
// Una función asíncrona que retorna un Future que eventualmente entregará un String
async fn obtener_datos_servidor(id: u32) -> String {
// Simulamos una latencia de red con un timer asíncrono
// .await suspende la función, liberando el hilo para otras tareas
sleep(Duration::from_secs(2)).await;
format!("Datos del usuario {}", id)
}
#[tokio::main] // El atributo macro inicia el runtime de Tokio
async fn main() {
println!("Iniciando petición...");
// Un bloque async crea un nuevo Future sin ejecutar el código aún
let tarea_pendiente = async {
println!("Preparando datos...");
let resultado = obtener_datos_servidor(42).await;
resultado
};
println!("La tarea está creada, pero aún no se ha ejecutado.");
// El uso de .await aquí es lo que realmente impulsa la ejecución
// Se suspende main hasta que tarea_pendiente termine
let mensaje_final = tarea_pendiente.await;
println!("Resultado recibido: {}", mensaje_final);
}
Análisis del código
async fn obtener_datos_servidor(id: u32) -> String: La palabra claveasyncindica que esta función devuelve un tipo que implementaFuture<Output = String>. No devuelve elStringdirectamente, sino la promesa de obtenerlo.sleep(Duration::from_secs(2)).await: Aquí,sleepes una función asíncrona. Al usar.await, la funciónobtener_datos_servidorse suspende. El hilo de ejecución no se queda bloqueado esperando 2 segundos; en su lugar, el runtime puede usar ese hilo para otras cosas.#[tokio::main]: Dado que Rust no tiene un runtime integrado, este atributo de la bibliotecatokiocrea el entorno necesario para ejecutar el código asíncrono y gestionar el bucle de eventos (event loop).let tarea_pendiente = async { ... };: Esto crea un bloque asíncrono. Es una expresión que produce unFuture. En este punto, el código dentro de las llaves{}no se ha ejecutado. Solo se ha construido la máquina de estados.let mensaje_final = tarea_pendiente.await;: El operador.awaites el punto de suspensión. Aquí es donde el runtime comienza a “pollear” (consultar) la máquina de estados detarea_pendiente. La ejecución demainse pausa hasta que elFuturealcanza el estadoPoll::Ready.mensaje_final: Al terminar elawait, el valor resultante delFuturese extrae y se asigna a la variablemensaje_finalcon el tipoString.
N° 84