38 - Programación asincrónica en Rust - primeros pasos

38 - Programación asincrónica en Rust - primeros pasos

¡Bienvenidos de nuevo a Rustaceo.es! Hoy exploraremos la programación asincrónica en Rust, un enfoque moderno para manejar tareas concurrentes de manera eficiente sin bloquear el hilo principal. Rust proporciona un sistema asincrónico basado en async y await, permitiendo escribir código concurrente sin los riesgos de la programación multihilo tradicional.


🚀 ¿Por qué programación asincrónica? #

La programación asincrónica es útil cuando queremos: ✔ Manejar múltiples conexiones de red sin bloquear el programa. ✔ Ejecutar tareas en paralelo sin crear demasiados hilos del sistema. ✔ Mejorar la escalabilidad de aplicaciones como servidores web o procesamiento de datos.

Rust utiliza un modelo basado en promesas (futuras), donde las tareas se ejecutan en un runtime asincrónico.


🔀 Async y await: la base de la asincronía en Rust #

Para escribir código asincrónico en Rust, usamos async y await.

📌 Ejemplo: función asincrónica básica #

async fn decir_hola() {
    println!("¡Hola desde una función asincrónica!");
}

#[Tokio::main]
async fn main() {
    decir_hola().await;
}

🔹 async fn: Define una función asincrónica. 🔹 await: Espera a que la función finalice sin bloquear el hilo principal. 🔹 #[tokio::main]: Indica que main es asincrónico y ejecuta el runtime Tokio.


⚡ Usando tokio para tareas asincrónicas #

En Rust, el estándar no incluye un runtime asincrónico, por lo que usamos Tokio o async-std.

📌 Ejecutando múltiples tareas concurrentes #

use tokio::time::{sleep, Duration};

async fn tarea_rapida() {
    println!("Tarea rápida completada");
}

async fn tarea_lenta() {
    sleep(Duration::from_secs(2)).await;
    println!("Tarea lenta completada");
}

#[Tokio::main]
async fn main() {
    let t1 = tokio::spawn(tarea_rapida());
    let t2 = tokio::spawn(tarea_lenta());
    
    t1.await.unwrap();
    t2.await.unwrap();
}

🔹 tokio::spawn: Lanza tareas en paralelo dentro del runtime de Tokio. 🔹 sleep().await: Simula una tarea que tarda en completarse sin bloquear el programa.


🔄 Streams: manejo de datos asincrónicos #

A veces necesitamos procesar flujos de datos sin esperar a que terminen completamente. Para esto usamos Streams.

📌 Procesando datos con streams #

use tokio_stream::StreamExt;

#[Tokio::main]
async fn main() {
    let mut stream = tokio_stream::iter(vec!["Item1", "Item2", "Item4"]);
    
    while let Some(pokemon) = stream.next().await {
        println!("¡Nuevo Pokémon encontrado: {}!", pokemon);
    }
}

🔹 tokio_stream::iter: Convierte un Vec<T> en un stream asincrónico. 🔹 next().await: Extrae elementos del stream uno a uno de manera asincrónica.


🔥 Integrando async con un servidor http #

Un caso de uso común es manejar solicitudes HTTP sin bloquear el servidor.

📌 Servidor web asincrónico con warp #

use warp::Filter;

#[Tokio::main]
async fn main() {
    let ruta = warp::path!("saludar" / String)
        .map(|nombre: String| format!("¡Hola, {}!", nombre));
    
    warp::serve(ruta).run(([127, 0, 0, 1], 3030)).await;
}

🔹 warp::serve(): Crea un servidor HTTP asincrónico. 🔹 Rutas dinámicas: Responde a http://127.0.0.1:3030/saludar/Rust con ¡Hola, Rust!.


🏆 Conclusión #

Rust ofrece un sistema asincrónico potente y seguro que nos permite escribir código concurrente sin los errores comunes de los hilos tradicionales.

async y await: Manejo claro y eficiente de tareas asincrónicas. ✔ Tokio y async-std: Runtimes para ejecutar código asincrónico. ✔ Streams y warp: Procesamiento de datos y servidores web sin bloqueo.

🔮 Próximo paso: Profundizar en patrones avanzados como select!, async-trait y comunicación entre tareas asincrónicas.

¡Hasta la próxima, Rustaceos!