hiro99ma blog

rust: 非同期とtokio

「ジュリ~!」、というわけで今回は Rust の非同期関連だ。
TOKIO といえば沢田研二氏だと思う。

使っているライブラリに async.await、あと #[tokio::main] が出てきたので調べておく。

async / await は昔は無かった

昔購入した Rust の本があるのだが、それにはキーワードも含めて asyncawait が載っていなかった。 おそらく The Rust Programming Language 日本語版 を冊子にしたものなので、 途中から追加されたキーワードなのだろう。
原本には Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams の章があるので そちらを読んだ方がよいか。

技術書の翻訳だと原文の方が意味が通じやすかったり、あとで検索するときのために英語での表現を知っていた方が助かったりすることもある。 しかし初学者(かつ英語苦手)にとっては、とりあえず AI に尋ねてみるか、本家筋に近い日本語の資料を手始めにしそうだ。
自動翻訳は便利なのだが、文字だけだと例えば Future のような普通の英単語にもあるキーワードを「将来」と訳したり「フューチャー」とカタカナになっていたりすると読む人に迷いが生じてしまう。 原文も Future とキーワードっぽく書いてあったり、タイトルでは “Futures” と複数になっていたり、イタリック小文字で “futures” と書いてあったりでわざとそうしているのか私では判断できなかった。

ともかく原本の 17章を読むのがよさそうだ。 16章の「恐れるな!並行性(Fearless Concurrency)」も読まないといかんだろう。
タイトルだけでも書いておいて、自分の読む気を高めよう。

てきとーな予備知識…

「The Rust Programming Language 日本語版」にキーワードとしては載っていたので抜粋。

ライブラリで使う必要があったのでサンプルコードを見てなんとなくは使っている。
感覚的にこのようなものか。

「適当」とか「いい加減」って元の文字からすると悪い意味はなさそうなのだが、口頭で使うと悪い方の意味になってしまいがちだ。 良い方の意味にするなら「適切」とか「よい具合」とかか?
悪い方の意味なら「てきとー」とか「いーかげん」みたいな力の抜けた口頭のような書き方がよいのかもしれん。

async / await / Future

例えば、これはエラーにはならないが warning が出る。
実行すると何も出力されずに終わる。

async fn hello() {
    println!("hello");
}

fn main() {
    hello();
}

warning はこういう内容だ。
unused な Future の実装と言われている。

warning: unused implementer of `Future` that must be used
 --> src/main.rs:6:5
  |
6 |     hello();
  |     ^^^^^^^
  |
  = note: futures do nothing unless you `.await` or poll them
  = note: `#[warn(unused_must_use)]` on by default

これで .await を付ければよいかというと、それはそれでエラーになる。
呼び出している main()async じゃないのでダメなのだ。

error[E0728]: `await` is only allowed inside `async` functions and blocks
 --> src/main.rs:6:13
  |
5 | fn main() {
  | --------- this is not `async`
6 |     hello().await;
  |             ^^^^^ only allowed inside `async` functions and blocks

じゃあ main()async にすればよいかというと、それもエラーになる。
どうしろというんだー。

error[E0752]: `main` function is not allowed to be `async`
 --> src/main.rs:5:1
  |
5 | async fn main() {
  | ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`

ライブラリのサンプルコードでは tokio::main を使っていたのでまねをする。

$ cargo add tokio --features "macros,rt-multi-thread"
async fn hello() {
    println!("hello");
}

#[tokio::main]
async fn main() {
    hello().await;
}

17.1章 を読むとわかりやすい。
出てくる trpl クレートはこの章の説明用に作られたものなのでそのまま使えず tokio を使ったのである。
非同期のコードを実行するにはランタイムが必要だけど、ランタイムは Rust の標準にないから別途クレートで入手するなり作るなりしないといけないらしい。 まあ、この章のためにランタイムを用意しているくらいだからシンプルであれば簡単なのかも? と思ったが tokio を使っていると書いてあったのでそうでもないんだろう。
理由は 17.1 章にちょっとだけ書いてあるので読んでくだされ。たぶんもっといろいろ理由はあるんだろう。

async を付けた関数は impl std::future::Future<Output = 戻り値> という戻り値を持つのと同じになる…と読み取ったのだが、あまり自信がない。
vscode の補助機能ではそういう型を持つのと同じように見えるが、実際に書いてみるとコンパイルエラーになる。

image

単に let で受け取って使う分には問題ないようだ。

#[tokio::main]
async fn main() {
    let f = hello();
    f.await;
}

.await は非同期が終わるまで待つというよりもランタイムに処理を返すところらしい。 await point と書いてあった。 そう考えると非同期関連はほとんどランタイムがやっているのかね。
そして「処理を返す(back to the runtime)」ということは、制御の本体はランタイム側? まあ main() がエンドポイントであることには変わりは無いだろうから気にしなくてよいか。

ChatGPT氏にランタイムをいくつか列挙してもらった。

tokio が強いが、Linux 5.1 以降のカーネルが提供する非同期I/Oインターフェースの io_uring を直接利用するタイプはスループットの高さから使われるようになるかもみたいな評価だった。
よくある selectepoll を使っていたところを io_uring なるもので置き換えられそうな感じ。
Linux 専用になるという点は気にする必要があるだろうが、気にはなるね。

(WIP)

writer: hiro99ma
tags: Rust言語

 
About me
About me
comment
Comment Form
🏠
Top page
GitHub
GitHub
Twitter
X/Twitter
My page
Homepage