De nombreux langages systèmes ont un support de base pour les opérations d'entrée/sortie, mais ne font souvent pas de distinction claire entre les approches synchrones et asynchrones. Rust a été conçu dès le départ pour garantir la sécurité et la performance, y compris dans la gestion de l'I/O. Ainsi, la bibliothèque standard de Rust (std) ne propose que l'I/O synchrone, tandis que le support asynchrone populaire est mis à disposition dans des bibliothèques externes (comme tokio, async-std).
Mélanger les approches d'I/O synchrone et asynchrone entraîne souvent des difficultés pour maintenir le code et des problèmes de sécurité et de performance, car ces approches reposent sur des modèles différents de gestion des ressources, des threads et des blocages. Par exemple, lire directement un grand fichier ou attendre des données provenant du réseau peut bloquer le thread d'exécution (y compris le principal), ralentissant ainsi l'application.
Rust propose une séparation claire : Bibliothèque standard (std::io) — uniquement I/O synchrone avec un traitement des erreurs sécurisé et un contrôle strict de la possession des ressources. Pour les solutions asynchrones, on utilise des bibliothèques externes et des runtimes asynchrones — elles fournissent leurs propres types et API, mais avec une sémantique d'erreur et de sécurité similaire.
Exemple de code pour la lecture synchrone d'un fichier :
use std::fs::File; use std::io::{self, Read}; fn main() -> io::Result<()> { let mut file = File::open("foo.txt")?; let mut contents = String::new(); file.read_to_string(&mut contents)?; println!("{}", contents); Ok(()) }
Exemple de code pour la lecture asynchrone d'un fichier avec tokio :
use tokio::fs::File; use tokio::io::AsyncReadExt; #[tokio::main] async fn main() -> tokio::io::Result<()> { let mut file = File::open("foo.txt").await?; let mut contents = String::new(); file.read_to_string(&mut contents).await?; println!("{}", contents); Ok(()) }
Caractéristiques clés :
Peut-on mélanger directement les types (par exemple, File de std et File de tokio) pour le passage entre les fonctions synchrones et asynchrones ?
Non. Ils sont incompatibles au niveau de l'API, et les types standards n'implémentent pas les traits asynchrones.
Le thread std::thread::spawn est-il bloqué dans une fonction asynchrone si on y appelle une I/O synchrone ?
Oui. Si une I/O synchrone est appelée dans un environnement asynchrone, le thread sera bloqué, ce qui annule les avantages de l'asynchronicité.
Peut-on utiliser async fn main sans runtime (tokio ou async-std) ?
Non. Le point d'entrée asynchrone doit être exécuté par un runtime spécial, sinon le compilateur n'autorisera pas l'utilisation de async fn main.
Dans un serveur multithread Rust, on utilise une lecture synchrone de std::io dans des gestionnaires de requêtes asynchrones. Cela bloque la boucle d'événements, augmentant les latences, le serveur ne manage pas sous des charges de pointe.
Avantages :
Inconvénients :
On utilise uniquement des types asynchrones pour toutes les opérations de fichiers et de réseau dans le code asynchrone, en veillant strictement aux types et en gérant les erreurs à travers Result.
Avantages :
Inconvénients :