async/.await 入門

async/.await は、Rust の組み込みツールで、非同期関数でありながら同期的にふるまう処理を記述できます。async はコードブロックを Future トレイトを実装したステートマシンに変換します。
一方で、同期メソッドでブロッキング関数を呼び出すと、スレッド全体がブロックされてしまいます。ブロックされた Future はスレッドの制御を放棄し、他の Future が実行できるようになります。

それでは、Cargo.toml ファイルにいくつかの依存関係を追加してみましょう:

[dependencies]
futures = "0.3"

非同期関数を作成するには、async fn 構文を使用します:

#![allow(unused)]
fn main() {
async fn do_something() { /* ... */ }
}

async fn の戻り値は Future であり、Future は executor 上で実行される必要があります。

// `block_on` は、提供された Future が実行されるまで、現在のスレッドをブロックします。
// 完了するまで現在のスレッドをブロックします。他の executers は、より複雑な動作を提供します。
// 複数の futures を同じスレッドにスケジューリングするような、より複雑な動作を提供します。
use futures::executor::block_on;

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

fn main() {
    let future = hello_world(); // 何も表示されない
    block_on(future); // `future` が実行され、"hello, world!" が出力される
}

async fn 内で .await を使用すると、 Future トレイトを実装している別の型の完了を待つことができます。
block_on とは異なり、 .await は現在のスレッドをブロックするのではなく、非同期に Future が完了するのを待ちます。Future が完了すると、他のタスクを実行できるようになります。

例えば、3 つの async fnとして learn_song(訳注:歌を覚える)、sing_song(訳注:歌を歌う)、dance(訳注:踊る)がある場合を考えます:

async fn learn_song() -> Song { /* ... */ }
async fn sing_song(song: Song) { /* ... */ }
async fn dance() { /* ... */ }

歌を覚える、歌を歌う、踊る動作を扱うために、それぞれ個別のブロック処理とします。

fn main() {
    let song = block_on(learn_song());
    block_on(sing_song(song));
    block_on(dance());
}

しかし、この方法では、最高のパフォーマンスを発揮することはできません。なぜなら、一度に 1 つの処理しか行えないからです!
一方、私たちは歌を覚えてから歌うのは当然ですが、歌を覚えて歌を歌うのと同時に踊ることも可能です。
この方法を実現するためには、2つの独立した async fn を作成し、それらを同時実行します。

async fn learn_and_sing() {
    // 歌を学ぶまで待ってから歌います。
    // ここでは `block_on` ではなく `.await` を使用し、スレッドのブロックを防いでいます。
    // スレッドをブロックしないようにするため、`.await` を使用しています。これにより、`dance` を同時に行うことができます。
    let song = learn_song().await;
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();

    // `join!` は `.await` のようなものだが、複数の futures を同時に待つことができます。
    // `learn_and_sing` の future で一時的にブロックされた場合、 `dance` が現在のスレッドを引き継ぎます。
    // もし `dance` がブロックされた場合、future が現在のスレッドを引き継ぎます。
    // もし両方の未来がブロックされた場合、`learn_and_sing` が引き継ぐことができます。
    // `async_main` がブロックされ、executor に引き継がれます。
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}

この例では、歌を覚えるのは歌を歌う前でなければなりませんが、歌を覚えるのも歌を歌うのも踊る処理と同時に行うことができます。 もし、 learn_and_sing の関数内で learn_song().await ではなく block_on(learn_song()) を使用した場合、 learn_song が実行されている間、スレッドは他の処理ができなくなります。これでは、同時に踊ることは不可能です。
.await の場合、 learn_song がブロックされた際に、他のタスクが現在のスレッドを引き継ぐことができます。したがって、同じスレッドで複数の Future を同時に実行できます。