「あの人も読んでる」略して「も読」。さまざまな寄稿者が最近気になった情報や話題をシェアする企画です。他のテックな人たちがどんな情報を追っているのか、ちょっと覗いてみませんか?
みなさんこんにちは。
「あの人も読んでる」、第14回目の投稿です。maguro (X @yusuktan)がお届けします。
あっという間に2025年が終わってしまいました。2025年末もさまざまな技術トピックがありましたが、個人的にRust周りの動きが印象に残っています。今回は、そんなRustエコシステムに関連する2つのトピックをご紹介していきます。
compio――Rust非同期ランタイムの新たな選択肢
Rustの非同期ランタイムと言えば、多くの人はTokioを思い浮かべるでしょう。デファクトスタンダードであり、豊富なエコシステムを持つTokioは、Rustで非同期プログラミングを行う際には間違いなく第一選択肢になります。
もちろん、Tokio以外にもasync-std、smol、glommio、monoioといった選択肢は存在しますが、メンテナンス状況やエコシステムの豊富さを考えると、特にプロダクション運用を念頭に置いた場合は実質的にTokio一択かと思います。
そんな中、Redditの以下のスレッドが目に留まりました。
Compio instead of Tokio - What are the implications?
このスレッドでは、メッセージストリーミングプラットフォームである Apache Iggyが、0.6.0のリリースに合わせてTokioからcompioへと移行したことについて語られています。このことについて、Iggy 0.6.0のリリースポスト では以下のようにまとめられています。
The most significant change in 0.6.0 is the complete server rewrite using io_uring with a thread-per-core, shared-nothing architecture. This represents a fundamental shift from the previous Tokio-based poll model to a completion-based async runtime using compio.
Key aspects of this architectural change:
・Completion-based I/O - unlike poll-based models, io_uring uses completion queues where the kernel notifies when operations finish
・Thread-per-core design - each CPU core runs its own event loop with dedicated resources, eliminating cross-thread synchronization overhead
・Shared-nothing model - data is partitioned across cores to minimize contention and cache invalidation
和訳:
0.6.0で最も大きな変更点は、io_uring を使用したサーバーの完全な書き換えです。これは thread-per-core、shared-nothing architecture を採用しています。以前のTokioベースのポーリングモデルから、compio を使用した完了ベースの非同期ランタイムへと根本的に転換しました。
このアーキテクチャ変更の主なポイント:
・完了ベースI/O - ポーリングベースのモデルとは異なり、io_uring は完了キューを使用し、カーネルが操作の完了を通知
・ thread-per-core設計 - 各CPUコアは専用のリソースを持つ独自のイベントループを実行し、スレッド間の同期オーバーヘッドを排除
・shared-nothingモデル - 競合やキャッシュの無効化を最小限に抑えるために、データはコアごとに分割して配置
デファクトスタンダードであるTokioから新進気鋭のcompioへの移行ということで、かなり野心的な試みに思えます。なぜそのような決定に至ったのかを、Redditのスレッドを参照しながらTokioとcompioのアーキテクチャの違いに着目して見ていきたいと思います。
compio
compioは「completion-based IO」の略で、その名の通り、完了ベースのI/Oモデルを採用した非同期ランタイムです。ByteDanceが開発したmonoioにインスパイアされて作られました。
TokioとcompioのI/Oモデルの違い
リリースノートでも「Tokioはポーリングベース、compioは完了ベース」という表現が出てきました。この2つのアプローチをまず理解する必要があります。
Tokio(reactor):
- ファイルディスクリプタやソケットが「準備完了」になるのを待つ
- 準備完了になったら、カーネルから「準備完了になったよ」という通知が来る
- 通知を受信して、I/O操作を自分たちで行う
compio(proactor):
- I/O操作とバッファをカーネルに渡す
- カーネルが操作を完了するのを待つ
- 完了通知を受け取る
「準備完了の通知を受けた後、I/O処理を自分たちで行う」のがTokioのスタイル、一方「I/O操作に必要なバッファをカーネル側に渡してしまって、カーネル側にI/O処理まで任せてしまう」のがcompioのスタイル、というわけです。LinuxのAPIの観点から言うと、Tokioはepollベース、compioはio_uringベース、ということになります。
このアプローチの違いは、RustのAPIにも影響を及ぼします。具体例を見ながら説明します。
Tokioでは、I/Oを行う際はバッファへの可変参照 &mut buf を引数に渡します。
use tokio::io::AsyncReadExt as _;
#[tokio::main]
async fn main() {
let mut file = tokio::fs::File::open("foo.txt").await.unwrap();
let mut buf = [0; 64];
let n = file.read(&mut buf[..]).await.unwrap();
println!("{}", String::from_utf8_lossy(&buf[..n]));
}
このAPIスタイルはTokioではうまくいきますが、もしcompioで同じスタイルだとすると、厄介なことが起きます。
上で説明した通り、compioのセマンティクスでは、I/Oによる操作の対象となるバッファがカーネルに渡されます。カーネルからの完了通知がくるまでは、このバッファを生かしておかなければなりませんし、このバッファに対する読み書きもしてはなりません。
この制約は、通常のケースにおいては問題になりません。Rustの最大の特徴である「ライフタイム」と「Shared XOR Mutable」(不変参照と可変参照は同時には存在できない)によって &mut buf が存在している間は、このバッファに対する読み書きを他の場所から行うことはできないことが保証されるからです。
さて、一方でRustのFutureはキャンセルをすることができます。Futureをドロップするだけでキャンセルが完了するのでシンプルで簡単です。キャンセルされたら、&mut buf を握っていたFutureがなくなるので、buf に対する読み書きや解放などを行えるようになります。
しかしここで言う「キャンセル」はあくまでRustの世界での話です。実際に発行されたio_uringのリクエストは、一応キャンセルはできるものの、ベストエフォートとされています。特にio_uring_enter(2)には「ディスクI/Oリクエストは、すでに開始されてしまっている場合、キャンセルすることはできない」と書かれています。
つまり、
- 非同期I/Oをキャンセルしたい
&mut bufを握っていたFutureをドロップする- Rustの世界では
bufを自由に使えるようになる(読み書き、解放など) - しかし、カーネルに対するキャンセルの要求は、常に可能とは限らない。依然カーネルによる
bufに対する読み書きが行われる可能性がある
3でRust側で buf を解放して、そのあとに4でカーネルが buf に対して読み書きをしたら、まさしく "use after free" で、深刻な脆弱性の問題につながります。
そこで compio では、&mut buf ではなく buf を受け取るようなAPIにすることで、この問題を解決しています。
compio (v0.17.0) での具体例を見てみます。
use compio::io::AsyncReadAt as _;
#[compio::main]
async fn main() {
let file = compio::fs::File::open("foo.txt").await.unwrap();
let buf = Vec::with_capacity(64);
// ☆ &mut buf ではなく、bufを直接渡している
let (n, buf) = file.read_at(buf, 0).await.unwrap();
println!("{}", String::from_utf8_lossy(&buf[..n]));
}
☆の行に着目してください。&mut buf ではなく buf の所有権を渡しています。
こうすることで、read_at() が返してくるFutureをドロップ(キャンセル)した場合、bufに対する読み書きや解放が他の場所で発生するという心配はありません(read_at() の呼び出し側から buf に触るすべはもはやない)。
なお、カーネルに渡した buf の解放は、compio内部のドライバーが、io_uringからのレスポンス(キャンセル処理の完了通知)を受けてから行うため、bufのライフタイムに関する心配もありません。
なぜApache IggyはTokioからcompioに移行したのか
さて、TokioとcompioのI/Oモデルとそれに伴うAPIの違いを見てきました。では、Apache Iggy はなぜTokioからcompioに移行したのでしょうか。
この疑問に対して、RedditでApache Iggyのコア開発者の1人であるifmnz氏が回答しています。
https://www.reddit.com/r/rust/comments/1pn6010/comment/nu6dxsw
ifmnz氏の回答を要約すると、以下のようになります。
- thread-per-coreモデルの強み
- compioでは、各OSスレッドに1つのランタイムを実行し、スレッドをCPUコアにピン留め
- ほとんどの状態をスレッドごとに所有することで、キャッシュの局所性が向上
- Tokioもシングルスレッドなセットアップ(
RuntimeFlavor::CurrentThread/LocalSet)を提供しているので、それを利用することも可能ではある
- 完了ベースのランタイムの強み
- Iggyはネットワーク I/O とディスク I/O の両方が非常に多いワークロード
- compioのような完了ベースのランタイムでは、処理を事前にサブミットして完了通知を受け取るので、「準備完了」通知を受けてからread/writeシステムコールを発行するreactorと比べて、余分なポーリングを減らせる
- うまくbatch化すれば、システムコールの回数やウェイクアップを削減できる
- Tokioでは完了ベースI/Oの恩恵は得られない
- エコシステムの不足は否めない
- OpenTelemetryライブラリがそのままでは使えない、など
Rustにおける非同期ランタイムをどのように選ぶべきか
Apache Iggyが非同期ランタイムをcompioに切り替えた理由を見ると、我々がRustにおける非同期ランタイムを選択する際にも考慮したほうがよい(かもしれない)点がわかります。
- Tokioのデフォルトの仕組み(ワークスティーリング)が必ずしも適さないユースケースがある
- そのような場合、Tokio内でいくつか用意されている設定値(例えばRuntimeFlavorや max_blocking_threadsなど)を変えてみることで、ユースケースに適したアーキテクチャにできないかを検討してみる価値がある
- ポーリングベース(epoll) ではなく 完了ベース(io_uring) を使うのが適する場合は、Tokio以外のランタイムも検討する
- なお、Tokio における io_uring サポートはまだ不十分
- tokio-uring というクレートがあるが、あまりメンテナンスされていない
- tokio本体のリポジトリのほうでio_uringベースのfile I/Oの実装が進行中: Support io_uring for file I/O
- compioが有力な候補だが、tokioに比べてAPIがまだ安定していないことと、エコシステムがまだまだ成熟していないことは理解しておく必要がある
- なお、Tokio における io_uring サポートはまだ不十分
Rustのような低レイヤーまで制御できる言語を使うのであれば、コンピュータアーキテクチャやOSのレイヤーまで考慮に入れた技術選定をしていきたいなと改めて思いました。
Microsoft、2030年までにC/C++を全廃しRustへ移行?
次に紹介するのは、12月に報じられた衝撃的なニュースです。
MicrosoftのDistinguished EngineerであるGalen Hunt氏がLinkedInに投稿した求人情報が大きな話題を呼びました。「2030年までにMicrosoftからC/C++のすべてのコードを排除することが私の目標だ」という野心的な宣言と、それを実現するための「1エンジニア、1ヶ月、100万行のコード」という驚異的な目標が掲げられていたからです。
実際には研究プロジェクト
ただし、この投稿が予想以上の注目を集めたため、Hunt氏は追記で重要な訂正を行っています。
明確にしておきますが、WindowsがAIによってRustで書き直されているわけではありません。
私のチームのプロジェクトは研究プロジェクトです。AIとアルゴリズムによって言語間の移行を可能にする技術を開発しています。
つまり、これは「Microsoftの公式戦略」ではなく、言語間移行技術の研究開発プロジェクトということです。とはいえ、MicrosoftがAIとアルゴリズムを組み合わせた大規模コード変換インフラの構築に取り組んでいること、そしてRustへの移行を視野に入れていることは事実です。
MicrosoftのRustへの取り組み
今回の発表は、MicrosoftがここしばらくRustに積極的に投資してきた流れの延長線上にあります。
- 2022年: Azure CTOのMark Russinovich氏が、新規のC/C++プロジェクトを非推奨とし、Rustの使用を推奨
- 2023年: BlueHat IL 2023にて、WindowsカーネルにRustを導入する計画を発表
- 2025年: Russinovich氏がMicrosoftはRustに「オールイン」していると発言
このようにMicrosoftは段階的にRustへのシフトを進めており、Hunt氏のチームによる研究プロジェクトは、その動きをさらに加速させるための取り組みと言えるでしょう。
おわりに
今回は、Rustエコシステムに関連する2つのトピックをご紹介しました。
compioは、Tokioとは異なるアプローチで非同期I/Oに取り組む興味深いプロジェクトです。Apache IggyがTokioからcompioに移行したことは、最適な非同期ランタイム選びに対しての示唆を与えてくれるものでした。
また、MicrosoftのRust移行は、メモリ安全性への業界全体の関心の高まりを示しています。研究段階とはいえ、Microsoftほどの規模の企業がこうした取り組みを進めていること自体が、C/C++からRustへの移行が単なるトレンドではなく、長期的な方針であることの表れです。
2026年もRustエコシステムの動向を追っていきたいと思います。
また次回、おすすめコンテンツを紹介していきます。お楽しみに!
maguroさんの「も読」過去記事
- JSConf、YAPC、HonoConf――カンファレンスの秋を振り返る(12月9日公開)
- 初心者と熟練者、同じ5行のコードを見た時の視界の違い(9月19日公開)
- イギリス就職奮闘記とJavaScript CLIツールスタック2025(8月25日公開)
