はじめに
Rustで非同期処理するとき、ランタイム選びで迷いませんか?
tokio vs async-std
この2つが二大巨頭。でも「どっち使えばいいの?」って聞かれると意外と答えにくい。
本気で比較してみました。
目次
それぞれの特徴
tokio
[dependencies]
tokio = { version = "1", features = ["full"] }
特徴:
- 最も人気: crates.io のダウンロード数が圧倒的
- 高機能: ファイルI/O、タイマー、同期プリミティブなど全部入り
- 高パフォーマンス: 大量の並行接続に対応
- エコシステムが充実: axum, tonic, reqwest など
#[tokio::main]
async fn main() {
println!("Hello, tokio!");
}
async-std
[dependencies]
async-std = { version = "1", features = ["attributes"] }
特徴:
- std ライクなAPI: 標準ライブラリに近い設計
- 学習しやすい: std を知ってれば使える
- シンプル: 必要最低限の機能
#[async_std::main]
async fn main() {
println!("Hello, async-std!");
}
API比較
ファイル読み込み
tokio
use tokio::fs::File;
use tokio::io::AsyncReadExt;
async fn read_file() -> std::io::Result<String> {
let mut file = File::open("hello.txt").await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
async-std
use async_std::fs::File;
use async_std::io::ReadExt;
async fn read_file() -> std::io::Result<String> {
let mut file = File::open("hello.txt").await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
Ok(contents)
}
ほぼ同じ! async-std は std のAPIに合わせてるので、移行が楽。
スリープ
tokio
use tokio::time::{sleep, Duration};
async fn wait() {
sleep(Duration::from_secs(1)).await;
}
async-std
use async_std::task;
use std::time::Duration;
async fn wait() {
task::sleep(Duration::from_secs(1)).await;
}
タスク生成
tokio
let handle = tokio::spawn(async {
// 非同期処理
42
});
let result = handle.await.unwrap();
async-std
let handle = async_std::task::spawn(async {
// 非同期処理
42
});
let result = handle.await;
Mutex
tokio
use tokio::sync::Mutex;
let mutex = Mutex::new(0);
let mut guard = mutex.lock().await;
*guard += 1;
async-std
use async_std::sync::Mutex;
let mutex = Mutex::new(0);
let mut guard = mutex.lock().await;
*guard += 1;
チャネル
tokio
use tokio::sync::mpsc;
let (tx, mut rx) = mpsc::channel(32);
tx.send(42).await.unwrap();
let value = rx.recv().await;
async-std
use async_std::channel;
let (tx, rx) = channel::bounded(32);
tx.send(42).await.unwrap();
let value = rx.recv().await;
パフォーマンス
ベンチマーク(目安)
| シナリオ | tokio | async-std |
|---|---|---|
| 大量の短いタスク | ◎ | ○ |
| 長時間のI/O | ○ | ○ |
| メモリ使用量 | ○ | ○ |
| 起動時間 | ○ | ◎ |
結論: 大きな差はない。実用上はどちらでもOK。
tokio の強み
- io_uring サポート(Linux 5.1+)
- work-stealing スケジューラ
- 細かいチューニング可能
// ランタイムのカスタマイズ
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.unwrap();
async-std の強み
- シンプルなAPI
- std との互換性重視
- 学習コストが低い
エコシステム
tokio ベースのクレート
| クレート | 用途 |
|---|---|
| axum | Webフレームワーク |
| reqwest | HTTPクライアント |
| tonic | gRPC |
| sqlx | データベース |
| hyper | 低レベルHTTP |
| tower | ミドルウェア |
async-std ベースのクレート
| クレート | 用途 |
|---|---|
| tide | Webフレームワーク |
| surf | HTTPクライアント |
| async-tungstenite | WebSocket |
現実
tokio のエコシステムが圧倒的に大きい。
新しいクレートはだいたい tokio 対応が先。async-std は後からか、対応されないこともある。
ランタイム非依存のクレート
# 両方で使える
futures = "0.3" # Future ユーティリティ
async-trait = "0.1" # async トレイトメソッド
使い分けの指針
tokio を選ぶべき場合
✅ Webサーバー/APIを作る
- axum, actix-web, warp など主要フレームワークが tokio ベース
✅ gRPC を使う
- tonic は tokio 専用
✅ 既存の tokio ベースのクレートを使う
- reqwest, sqlx など
✅ 高パフォーマンスが必要
- 大量の並行接続を捌く
✅ チームで開発
- 情報量が多く、困ったときに調べやすい
async-std を選ぶべき場合
✅ std に近いAPIが好み
- 学習コストを抑えたい
✅ シンプルな非同期処理だけ
- 複雑な機能は不要
✅ tide/surf を使いたい
- async-std ベースの Webスタック
✅ tokio に依存したくない
- 何らかの理由で
実際のところ
迷ったら tokio。
理由:
- エコシステムが大きい
- 情報が多い
- 困ったときに解決策が見つかりやすい
- 採用実績が多い
混在させたい場合
async-compat
両方のコードを混ぜて使える:
[dependencies]
async-compat = "0.2"
use async_compat::Compat;
// async-std のコードを tokio で動かす
#[tokio::main]
async fn main() {
Compat::new(async_std_function()).await;
}
両対応のクレート
一部のクレートは両方に対応:
# sqlx: 両方対応
sqlx = { version = "0.7", features = ["runtime-tokio-native-tls"] }
# または
sqlx = { version = "0.7", features = ["runtime-async-std-native-tls"] }
比較まとめ
| 項目 | tokio | async-std |
|---|---|---|
| 人気 | ◎ | ○ |
| エコシステム | ◎ | △ |
| API設計 | 独自 | std風 |
| 学習コスト | やや高 | 低 |
| パフォーマンス | ◎ | ○ |
| 情報量 | ◎ | ○ |
| 柔軟性 | ◎ | ○ |
まとめ
結論
2025年現在、特に理由がなければ tokio を選ぶ。
- エコシステムの大きさ
- 情報の多さ
- 採用実績
これらで tokio が圧倒的。
async-std を選ぶ理由があるなら
- std風のAPIが好き
- tide/surf を使いたい
- シンプルさ重視
それは全然アリ。性能差はほぼない。
チェックリスト
- 使いたいクレートが何ベースか確認
- チームの慣れを考慮
- 迷ったら tokio
今すぐできるアクション
- 作りたいものに必要なクレートを調べる
- そのクレートが tokio/async-std どちらベースか確認
- それに合わせてランタイムを選ぶ
ランタイム選び、最初は悩むけど、実際はあまり大きな問題じゃないです。どっちを選んでも非同期Rustは書けます。
この記事が役に立ったら、いいね・ストックしてもらえると嬉しいです!