ProgrammationProgrammeur système

Comment fonctionne l'asynchronisme et la gestion des Future en Rust ? Quelles sont les caractéristiques uniques de l'implémentation async/await et en quoi cela diffère-t-il des mécanismes similaires dans d'autres langages ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

L'asynchronisme en Rust est réalisé via Future — un objet représentant un travail dont le résultat sera obtenu dans le futur. Les fonctions déclarées avec async fn retournent une structure anonyme de générateur implémentant le trait Future. Pour exécuter du code asynchrone, un runtime est nécessaire (par exemple, Tokio ou async-std), car la bibliothèque standard ne contient pas de boucle d'événements intégrée.

Les principales caractéristiques de Rust :

  • Abstractions sans coût — le compilateur transforme le code async en machine d'état sans allocations sur le tas, si la tâche le permet.
  • Absence de garbage collector natif — toutes les ressources sont gérées explicitement, ce qui nécessite une attention particulière aux lifetimes et à la propriété.
  • Send/Sync — des contraintes sur la transférabilité et la synchronisation des tâches entre les threads.

Exemple :

use tokio::time::sleep; use std::time::Duration; async fn foo() { println!("Hello"); sleep(Duration::from_secs(1)).await; println!("World!"); } #[tokio::main] async fn main() { foo().await; }

Question piège

Une fonction asynchrone en Rust peut-elle être exécutée immédiatement lors de l'appel par défaut ? Pourquoi ?

Réponse : Non. L'appel d'une async fn renvoie non pas un résultat, mais un objet de type Future (machine d'état). Pour une exécution réelle, il est nécessaire d'appeler .await ou de passer le Future au runtime. L'appel lui-même ne fait que créer une description de la tâche, sans l'exécuter.

Exemple :

async fn answer() -> u32 { 42 } let fut = answer(); // ici, il n'y a pas d'exécution, juste la création du future let result = fut.await; // l'exécution commence ici

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans un projet backend à fort chargement, un développeur junior a déclaré plusieurs fonctions async, mais n'a appelé .await nulle part. En conséquence, les flux principaux s'exécutaient de manière synchrone, ce qui a entraîné une chute de performance et un triplement des temps de réponse.

Histoire

Dans un microservice, l'API asynchrone a été utilisée avec Tokio. Lors de la migration, le développeur a essayé d'utiliser async-std et Tokio simultanément, oubliant que la boucle d'événements ne doit être qu'une seule. Cela a entraîné des blocages et des panic de runtime, car les deux runtimes étaient en conflit.

Histoire

L'un des membres de l'équipe a oublié les contraintes Send/Sync sur les types utilisés à l'intérieur du Future. En essayant de partager un future entre des threads, l'application a échoué avec une erreur de compilation exigeant l'implémentation de Send pour une certaine structure, ce qui a nécessité une révision de l'architecture de stockage d'état.