Introduction à la programmation asynchrone en Rust
La programmation asynchrone est une approche qui permet à votre application d'exécuter efficacement plusieurs tâches simultanément sans bloquer le thread d'exécution principal. En Rust, cette approche est implémentée via le modèle async/await, qui garantit la sécurité mémoire et les hautes performances caractéristiques du langage.
Contrairement au multithreading traditionnel, le code asynchrone fonctionne au sein d'un ou plusieurs threads, en basculant entre les tâches pendant les périodes d'attente (par exemple, les entrées-sorties ou les requêtes réseau). Cela le rend idéal pour créer des serveurs web à forte charge, des applications réseau et des outils de traitement de données.
Dans cet article, nous allons explorer les bases de la programmation asynchrone en Rust : des concepts clés aux exemples pratiques. Vous apprendrez comment fonctionnent Future, async et await, découvrirez le runtime populaire Tokio et écrirez votre premier projet asynchrone.
Concepts fondamentaux : Future, async et await
Qu'est-ce qu'un Future ?
En Rust, une tâche asynchrone est représentée par le trait Future. Un Future est une valeur qui peut ne pas être encore prête, mais qui sera obtenue dans le futur. Chaque Future possède une méthode poll qui vérifie si la tâche est terminée. Si la tâche est toujours en cours d'exécution, elle retourne Pending ; si elle est terminée, elle retourne Ready(value).
En lui-même, un Future n'effectue aucun travail. Il décrit seulement ce qui doit se produire. Pour exécuter du code asynchrone, un runtime est nécessaire — un exécuteur qui interroge les Future et gère leur exécution.
Les mots-clés async et await
Le mot-clé async transforme une fonction ordinaire en fonction asynchrone, qui retourne un Future. Par exemple :
async fn hello() -> String { "Привет, мир!".to_string()}Pour obtenir le résultat d'un Future, on utilise .await. Il suspend l'exécution de la fonction actuelle jusqu'à ce que le Future soit terminé :
let result = hello().await;println!("{}", result);Important : .await ne peut être utilisé qu'à l'intérieur d'une fonction asynchrone ou d'un bloc async {}.
Cycle de vie d'une tâche asynchrone
- Création d'un
Future(appel d'une fonction asynchrone). - Transmission du
Futureau runtime pour exécution. - Le runtime interroge le
Futureviapoll. - Si la tâche est bloquée (attente d'E/S), le runtime bascule vers une autre tâche.
- Lorsque le résultat est prêt, le
FutureretourneReady.
Écosystème : Tokio et async-std
Pour travailler avec du code asynchrone en Rust, on utilise des runtimes asynchrones. Les deux plus populaires sont Tokio et async-std.
Tokio
Tokio est le standard industriel pour la programmation asynchrone en Rust. Il fournit un ordonnanceur multithread, des entrées-sorties asynchrones, des minuteurs, de la synchronisation et des protocoles réseau. La plupart des grands projets (par exemple, Hyper, Tonic, Axum) sont construits sur Tokio.
Pour commencer avec Tokio, ajoutez la dépendance dans Cargo.toml :
[dependencies]tokio = { version = "1", features = ["full"] }La macro principale pour lancer du code asynchrone est #[tokio::main] :
#[tokio::main]async fn main() { println!("Hello from Tokio!");}async-std
async-std est un runtime alternatif qui cherche à ressembler le plus possible à la bibliothèque standard de Rust. Il est plus simple pour les débutants, mais moins populaire en production.
Exemple