Introduction à la programmation asynchrone en Rust
La programmation asynchrone est un paradigme qui permet d'effectuer des opérations d'entrée-sortie (requêtes réseau, lecture de fichiers, travail avec des bases de données) sans bloquer le thread d'exécution principal. En Rust, l'asynchronisme est implémenté via le système Future et les mots-clés async/await. Contrairement au multithreading, le code asynchrone s'exécute dans un seul thread, en basculant entre les tâches pendant l'attente.
Dans ce guide, nous aborderons :
- Les bases de
async/awaiten Rust - Le travail avec la crate
tokio— le runtime asynchrone le plus populaire - La création et la combinaison de
Future - Des exemples pratiques : un client HTTP asynchrone et des minuteurs
Cet article est destiné aux développeurs familiers avec la syntaxe de base de Rust. Si vous débutez, nous vous recommandons d'abord d'étudier les bases du langage sur PythonLib.
Bases de async/await et Future en Rust
Qu'est-ce qu'un Future ?
Future est un trait représentant une opération asynchrone qui peut se terminer dans le futur. En Rust, Future est paresseux : il ne fait rien tant qu'il n'est pas interrogé (poll). C'est le runtime asynchrone (par exemple, Tokio) qui est responsable de l'interrogation.
Un exemple simple de fonction asynchrone :
use std::time::Duration;
async fn say_hello() { println!("Bonjour du monde asynchrone !"); tokio::time::sleep(Duration::from_secs(1)).await; println!("1 seconde s'est écoulée");}
#[tokio::main]async fn main() { say_hello().await;}Le mot-clé async transforme la fonction en un Future. L'appel .await suspend l'exécution de la tâche actuelle jusqu'à la fin du Future, sans bloquer le thread.
Cycle de vie d'une tâche asynchrone
Lorsque vous appelez .await, voici ce qui se passe :
- Le runtime interroge le Future (appelle la méthode
poll()) - Si le Future est prêt — le résultat est retourné
- Si le Future attend (par exemple, une opération réseau) — le runtime bascule sur une autre tâche
- Lorsque la ressource devient disponible, le runtime reprend la tâche
Une particularité importante de Rust : .await ne peut être appelé qu'à l'intérieur d'une fonction asynchrone ou d'un bloc async {}. Tenter de l'utiliser dans un contexte synchrone entraînera une erreur de compilation.
Travail pratique avec Tokio
Installation et configuration
Tokio est un runtime asynchrone pour Rust, fournissant un planificateur de tâches, des minuteurs, des sockets réseau et bien plus encore. Pour commencer, ajoutez dans Cargo.toml :
[dependencies]tokio = { version = "1", features = ["full"] }Le flag features = ["full"] active toutes les fonctionnalités de Tokio. Pour la production, il est préférable de ne sélectionner que les fonctionnalités nécessaires.
Lancement de plusieurs tâches asynchrones
L'un des principaux avantages de l'asynchronisme est l'exécution parallèle de multiples tâches. Utilisez tokio::spawn pour lancer des tâches en arrière-plan :
use tokio::time::{sleep, Duration};
async fn task(name: &str, seconds: u64) { println!("Tâche {} a commencé", name); sleep(Duration::from_secs(seconds)).await; println!("Tâche {} terminée après {} sec", name, seconds);}
#[tokio::main]async fn main() { let handle1 = tokio::spawn(task("A", 2)); let handle2 = tokio::spawn(task("B", 1));
// Attend la fin des deux tâches let _ = tokio::join!(handle1, handle2); println!("Toutes les tâches sont terminées");}La macro tokio::join! attend la fin de tous les Future passés. Si une tâche se termine plus tôt, l'exécution n'est pas interrompue — nous attendons les autres.