Wiele języków systemowych ma podstawowe wsparcie dla operacji wejścia/wyjścia, ale często nie rozróżnia wyraźnie podejść synchronicznych i asynchronicznych. Rust od samego początku był projektowany z myślą o bezpieczeństwie i wydajności, również w kontekście operacji I/O. Dlatego w standardowej bibliotece Rust (std) zrealizowano tylko synchroniczne I/O, a popularne wsparcie asynchroniczne zostało przeniesione do osobnych zewnętrznych bibliotek (np. tokio, async-std).
Mieszanie podejść synchronicznych i asynchronicznych I/O często prowadzi do komplikacji w utrzymaniu kodu oraz problemów z bezpieczeństwem i wydajnością, ponieważ te podejścia mają różne modele zarządzania zasobami, wątkami i blokadami. Na przykład, bezpośrednie odczytywanie dużego pliku lub czekanie na dane z sieci może zablokować wątek wykonywania (w tym główny), spowalniając aplikację.
Rust oferuje wyraźne rozdzielenie: Standardowa biblioteka (std::io) — tylko synchroniczne I/O z bezpiecznym przetwarzaniem błędów i ścisłą kontrolą nad zasobami. Do rozwiązań asynchronicznych wykorzystuje się zewnętrzne biblioteki i asynchroniczne silniki wykonawcze — oferują one swoje typy i API, ale z podobną semantyką błędów i bezpieczeństwa.
Przykład kodu synchronicznego odczytu pliku:
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(()) }
Przykład kodu asynchronicznego odczytu pliku przez 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(()) }
Kluczowe cechy:
Czy można bezpośrednio mieszać typy (np. File z std i File z tokio) do przekazywania między funkcjami synchronicznymi a asynchronicznymi?
Nie. Są one niekompatybilne na poziomie API, a standardowe typy nie implementują asynchronicznych traitów.
Czy wątek std::thread::spawn jest blokowany w funkcji asynchronicznej, jeśli zostanie w niej wywołane synchroniczne I/O?
Tak. Jeśli w środowisku asynchronicznym wywoła się synchroniczne I/O, wątek zostanie zablokowany, co niweluje zalety asynchroniczności.
Czy można używać async fn main bez silnika wykonawczego (tokio lub async-std)?
Nie. Asynchroniczny punkt wejścia musi być uruchamiany przez specjalny silnik wykonawczy, w przeciwnym razie kompilator nie pozwoli na używanie async fn main.
W wielowątkowym serwerze w Rust wykorzystują synchroniczne odczyty z std::io w asynchronicznych handlerach żądań. Z tego powodu obciążenie blokuje pętlę zdarzeń, zwiększają się opóźnienia, serwer nie radzi sobie pod dużym obciążeniem.
Plusy:
Minusy:
Używają tylko asynchronicznych typów do wszystkich operacji na plikach i sieci w asynchronicznym kodzie, rygorystycznie dbając o typy i wychwytując błędy poprzez Result.
Plusy:
Minusy: