多くのシステム言語は入出力操作に対する基本的なサポートを持っていますが、しばしば同期的および非同期的アプローチの明確な違いを示しません。Rustは、安全性とパフォーマンスを両立させるために、I/O処理においても設計されています。したがって、Rustの標準ライブラリ(std)では、同期I/Oのみが実装されており、人気のある非同期サポートは別の外部ライブラリ(例:tokio、async-std)に外部化されています。
同期I/Oと非同期I/Oのアプローチを混在させることは、コードの保守の複雑さや安全性・パフォーマンスの問題を引き起こすことがよくあります。これらのアプローチは、リソース、スレッド、ロックに対する異なるモデルを持っているためです。たとえば、大きなファイルの直接読取やネットワークからのデータ待機は、実行スレッド(メインスレッドを含む)をブロックし、アプリケーションを遅延させる可能性があります。
Rustは明確な分離を提供します:標準ライブラリ(std::io) — 安全なエラーハンドリングと資源の厳格な所有権管理を持つ同期I/Oのみを提供します。非同期ソリューションには外部ライブラリと非同期ランタイムが使用され、それぞれ独自の型とAPIを提供しますが、エラーと安全性に関する意味は同様です。
同期ファイル読取のコード例:
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(()) }
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(()) }
主な特徴:
同期I/O関数と非同期関数の間で、たとえばstdのFileやtokioのFileのような型を直接混合することはできますか?
いいえ。それらはAPIレベルで互換性がなく、標準の型は非同期トレイトを実装していません。
同期I/Oが非同期関数内で呼び出されると、std::thread::spawnはブロックされますか?
はい。非同期環境で同期I/Oを呼び出すと、スレッドがブロックされ、非同期性の利点が無効になります。
ランタイムなしでasync fn mainを使用できますか(tokioやasync-std)?
いいえ。非同期エントリポイントは特別なランタイムで実行される必要があり、さもなければコンパイラはasync fn mainの使用を許可しません。
Rustのマルチスレッドサーバーでは、非同期リクエストハンドラ内でstd::ioからの同期読み取りを使用しています。このため、負荷がevent loopをブロックし、遅延が増加し、サーバーはピーク負荷に対応できなくなります。
利点:
欠点:
非同期コード内のすべてのファイルおよびネットワーク操作に対して、非同期型のみを使用し、型を厳密に管理し、Resultを介してエラーを捕捉します。
利点:
欠点: