Introducción a la programación asíncrona en Rust
La programación asíncrona es un paradigma que permite ejecutar operaciones de entrada/salida (solicitudes de red, lectura de archivos, trabajo con bases de datos) sin bloquear el hilo principal de ejecución. En Rust, la asincronía se implementa a través del sistema Future y las palabras clave async/await. A diferencia del multihilo, el código asíncrono funciona en un solo hilo, cambiando entre tareas durante la espera.
En esta guía analizaremos:
- Fundamentos de
async/awaiten Rust - Trabajo con la crate
tokio— el runtime asíncrono más popular - Creación y combinación de
Future - Ejemplos prácticos: cliente HTTP asíncrono y temporizadores
El artículo está dirigido a desarrolladores familiarizados con la sintaxis básica de Rust. Si recién comienzas, te recomendamos estudiar primero los fundamentos del lenguaje en PythonLib.
Fundamentos de async/await y Future en Rust
¿Qué es Future?
Future es un trait que representa una operación asíncrona que puede completarse en el futuro. En Rust, Future es perezoso: no hace nada hasta que se le consulta (poll). El runtime asíncrono (por ejemplo, Tokio) es el encargado de realizar la consulta.
Un ejemplo simple de función asíncrona:
use std::time::Duration;
async fn say_hello() { println!("¡Hola desde el mundo asíncrono!"); tokio::time::sleep(Duration::from_secs(1)).await; println!("Ha pasado 1 segundo");}
#[tokio::main]async fn main() { say_hello().await;}La palabra clave async convierte la función en un Future. La llamada .await suspende la ejecución de la tarea actual hasta que se complete el Future, sin bloquear el hilo.
Ciclo de vida de una tarea asíncrona
Cuando llamas a .await, ocurre lo siguiente:
- El runtime consulta el Future (llama al método
poll()) - Si el Future está listo — devuelve el resultado
- Si el Future está esperando (por ejemplo, una operación de red) — el runtime cambia a otra tarea
- Cuando el recurso está disponible, el runtime reanuda la tarea
Una característica importante de Rust: .await solo puede llamarse dentro de una función asíncrona o un bloque async {}. Intentar usarlo en un contexto síncrono provocará un error de compilación.
Trabajo práctico con Tokio
Instalación y configuración
Tokio es un runtime asíncrono para Rust que proporciona un planificador de tareas, temporizadores, sockets de red y mucho más. Para empezar, agrega a Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }La bandera features = ["full"] incluye todas las funcionalidades de Tokio. Para producción, es mejor seleccionar solo las características necesarias.
Ejecución de múltiples tareas asíncronas
Una de las principales ventajas de la asincronía es la ejecución paralela de múltiples tareas. Usa tokio::spawn para ejecutar tareas en segundo plano:
use tokio::time::{sleep, Duration};
async fn task(name: &str, seconds: u64) { println!("Tarea {} iniciada", name); sleep(Duration::from_secs(seconds)).await; println!("Tarea {} completada en {} seg", name, seconds);}
#[tokio::main]async fn main() { let handle1 = tokio::spawn(task("A", 2)); let handle2 = tokio::spawn(task("B", 1));
// Esperamos la finalización de ambas tareas let _ = tokio::join!(handle1, handle2); println!("Todas las tareas completadas");}La macro tokio::join! espera la finalización de todos los Future pasados. Si una tarea termina antes, la ejecución no se interrumpe — esperamos a las demás.