Cuando los eventos controlan el código: Event-driven en Rust
\n\nImagine que su código es un director de orquesta. En la programación síncrona clásica, el director decide cuándo debe sonar cada nota. En la arquitectura event-driven, el director simplemente reacciona a que los músicos ya han empezado a tocar. ¿Suena paradójico? Solo a primera vista.
\n\nLa arquitectura event-driven (orientada a eventos) es un enfoque en el que el flujo de ejecución del programa está determinado por eventos: clic del ratón, recepción de una solicitud HTTP, finalización de E/S, un temporizador. Rust, con su sistema de propiedad y abstracciones de costo cero, es el terreno ideal para dicha arquitectura. Especialmente si vienes de Python, donde el event loop (por ejemplo, en asyncio) funciona, pero a menudo choca con el GIL y la sobrecarga del tipado dinámico.
\n\nEn este artículo no solo hablaremos de teoría. Escribiremos código real, analizaremos la arquitectura event-driven en Rust utilizando crates populares y mostraremos cómo puede acelerar sus aplicaciones varias veces.
\n\n¿Por qué Rust y no Python para eventos?
\n\nPython es un excelente lenguaje para prototipado. Pero cuando necesita procesamiento de eventos en tiempo real con miles de conexiones simultáneas, Python comienza a ralentizarse. Rust, en cambio, ofrece:
\n\n- \n
- Abstracciones de costo cero — async/await no añade sobrecarga si no utiliza sus capacidades \n
- Ausencia de recolector de basura — no hay pausas repentinas al procesar eventos \n
- Sistema de tipos que detecta errores en tiempo de compilación \n
- Paralelismo real — no es multitarea cooperativa, sino hilos reales sin GIL \n
Si escribe aplicaciones event-driven en Rust, obtiene control sobre cada byte de memoria y cada ciclo de CPU. Y esto no es marketing, es un hecho.
\n\nFundamentos: Event Loop y procesamiento de eventos
\n\nEn el corazón de cualquier sistema orientado a eventos se encuentra el event loop (bucle de eventos). Es un bucle infinito que:
\n\n- \n
- Espera la aparición de un nuevo evento \n
- Lo extrae de la cola \n
- Lo pasa al manejador \n
- Repite \n
En Rust, el event loop estándar lo proporciona el crate tokio. Escribamos un ejemplo sencillo:
use tokio::sync::mpsc;\nuse tokio::time::{sleep, Duration};\n\n#[tokio::main]\nasync fn main() {\n // Creamos un canal para transmitir eventos\n let (tx, mut rx) = mpsc::channel::<String>(32);\n\n // Productor: genera eventos\n tokio::spawn(async move {\n for i in 0..5 {\n let event = format!("Evento nº{}", i);\n tx.send(event).await.unwrap();\n sleep(Duration::from_millis(500)).await;\n }\n });\n\n // Consumidor: procesa eventos\n while let Some(event) = rx.recv().await {\n println!("Procesado: {}", event);\n // Aquí puede ir su lógica de negocio\n }\n}\n\nEste código demuestra el patrón básico de programación event-driven en Rust: un componente genera eventos, otro los procesa. El canal mpsc (multi-productor, único-consumidor) garantiza la transferencia segura de datos entre tareas.
Ejemplo avanzado: sistema de procesamiento de pedidos
\n\nCompliquemos la tarea. Imagine que estamos escribiendo el backend de una tienda en línea. Cuando llega un nuevo pedido, necesitamos:
\n\n- \n
- Verificar la disponibilidad del producto \n
- Reservarlo \n
- Enviar una notificación \n
- Actualizar las estadísticas \n
El enfoque clásico es hacer todo esto de forma síncrona. El enfoque event-driven es dividirlo en eventos:
\n\nuse tokio::sync::broadcast;\nuse tokio::time::sleep;\nuse std::time::Duration;\n\n#[derive(Debug, Clone)]\nenum OrderEvent {\n NewOrder { id: u32, items: Vec<String>