Rustのasync、awaitを利用し、非同期プログラミングに入門する

Tags:

#Rust
このエントリーをはてなブックマークに追加

本記事はRustアドベントカレンダー2の17日目の記事です。 本記事ではRustのasync、awaitを使って、非同期プログラミングに入門してみたいと思います。

以下のプログラムではfuturesクレートを利用しています。

https://github.com/rust-lang/futures-rs

async

まずはasyncを使った例を見てみましょう。

use futures::executor::block_on;

async fn hello_world() {
    println!("hello world")
}

fn main() {
    let future = hello_world();
    block_on(future);
}

上記の例はhello worldの文字列を出力するプログラムを、あえてasyncキーワードを利用し記述したものです。

関数が非同期であることを明示するために、asyncを利用します。 block_on関数は非同期処理の実行が終了するまで、処理をブロックするという関数です。

このプログラムを実行するとコンソールにhello worldの文字列が出力され、終了します。

await

次はawaitの例を見てみましょう。awaitは非同期処理の結果を待つことを明示するためのキーワードです。 awaitを使うことでasyncで書かれた非同期関数が完了するまで待ち合わせます。 awaitはasyncスコープ内でのみ利用可能です。

以下の関数はhello worldとhello world2を順に出力します。

use futures::executor::block_on;

async fn hello_world() {
    println!("hello world")
}

async fn hello_world2() {
    println!("hello world2")
}

async fn run() {
    hello_world().await;
    hello_world2().await;
}

fn main() {
    block_on(run())
}

今回の例ではrun関数の中で、hello_world関数をawaitし、その後にhello_world2関数をawaitしています。 そのため必ずhello worldの後にhello world2が出力されます。

非同期関数を並行に実行

次は非同期関数を並行に実行する例を見てみましょう。 イメージとしてはJavaScriptのPromise.all関数のようなものです。 ここではfuturesクレートのjoinマクロを利用します。 joinマクロを利用して、先ほどの関数を書き直してみます。

use futures::executor::block_on;
use futures::join;

async fn hello_world() {
    println!("hello world")
}

async fn hello_world2() {
    println!("hello world2")
}

async fn run() {
    join!(hello_world(), hello_world2());
}

fn main() {
    block_on(run())
}

こうすることによってhello_world関数とhello_world2関数が並行に実行されます。理論的にはhello worldとhello world2はどちらが先に出力されるかわかりません。

非同期関数を並行に実行2

非同期関数を並行に実行する別の例を見てみましょう。次の例では複数の非同期関数を実行し、どれか一つが完了した時に処理を抜け出します。

イメージとしてはJavaScriptのPromise.race関数のようなものです。

ここではfuturesクレートのselectマクロを利用しています。

use futures::executor::block_on;
use futures::pin_mut;
use futures::select;
use futures::FutureExt;

async fn hello_world() -> String {
    "hello world".to_string()
}

async fn hello_world2() -> String {
    "hello world2".to_string()
}

async fn run() {
    let hello_world_fused = hello_world().fuse();
    let hello_world2_fused = hello_world2().fuse();

    pin_mut!(hello_world_fused, hello_world2_fused);

    select! {
        a = hello_world_fused => println!("{}", a),
        b = hello_world2_fused => println!("{}", b)
    }
}

fn main() {
    block_on(run())
}

selectマクロは複数の非同期関数を受け取り、どれか一つの関数が完了したら処理を抜け出します。この例ではhello_worldとhello_world2関数を並行に実行し、どちらかが完了したら終了します。

selectマクロは<pattern> = <expression> => <code>のフォーマットで入力を受け取ります。selectマクロに渡す非同期関数はUnpinトレイトとFusedFutureトレイトを満たす必要があるため、fuse関数とpin_mutマクロを利用しています。

このプログラムを実行するとコンソールにhello worldの文字列が出力され終了しました。

まとめ

本記事ではRustのasync、awaitを利用し、非同期プログラミングに入門してみました。 今回は非同期プログラミングの本当に触りの部分のみ調べてみましたが、まだまだRustの非同期プログラミングは奥が深そうです。

Rustの非同期プログラミングを使いこなせるようにこれからも精進したいと思います。最後までお読みいただきありがとうございました。