Rust 非同期プログラミング async/await

現代のプログラミングでは、非同期プログラミングはますます重要になっています。I/O 操作(ファイルの読み書き、ネットワーク通信など)の待機中にプログラムがブロックされず、パフォーマンスと応答性を向上させることができるからです。

非同期プログラミングは、Rust でノンブロッキング操作を処理する方法であり、プログラムが長時間の I/O 操作を実行している間もブロックされず、待機中に他のタスクを実行できるようにします。

Rust は非同期プログラミングを実現するためのさまざまなツールとライブラリを提供しています。含まれるもの:asyncとawaitキーワード、futuresおよび非同期ランタイム(tokio、async-std など)、その他の補助ツール。

  • Future:Future は Rust で非同期操作を表す抽象です。これは、まだ完了していない可能性のある計算であり、将来のある時点で値またはエラーを返します。

  • async/await:asyncキーワードは、非同期関数を定義するために使用され、Future を返します。awaitキーワードは、現在の Future の実行を、それが完了するまで一時停止するために使用されます。

例

以下の例は、async と await キーワードを使用して非同期関数を作成する方法と、非同期関数内で非同期タスクを実行してその完了を待つ方法を示しています。

例

// 必要な依存ライブラリをインポートする
use tokio;
use tokio::time::{self, Duration};

// 非同期関数、非同期タスクをシミュレートする
async fn async_task() -> u32 {
    // 非同期操作をシミュレートし、1 秒間待機する
    time::sleep(Duration::from_secs(1)).await;
    // 結果を返す
    42
}

// 非同期タスク実行関数
async fn execute_async_task() {
    // 非同期タスクを呼び出し、その完了を待つ
    let result = async_task().await;
    // 結果を出力する
    println!("Async task result: {}", result);
}

// メイン関数
#[tokio::main]
async fn main() {
    println!("Start executing async task...");
    // 非同期タスク実行関数を呼び出し、その完了を待つ
    execute_async_task().await;
    println!("Async task completed!");
}

上記のコードでは、まず非同期関数を定義しました。async_task()この関数は非同期操作をシミュレートし、tokio::time::delay_for()メソッドを使用して1秒間待機し、結果42を返します。次に、非同期タスク実行関数を定義しました。execute_async_task()その中で非同期関数を呼び出し、awaitキーワードを使用して非同期タスクの完了を待ちます。最後に、main関数内でtokio::mainマクロを使用して非同期タスク実行関数を実行し、その完了を待ちます。

このプログラムを実行すると、プログラムが非同期タスクの実行を開始したというメッセージを出力し、1秒間待機した後に非同期タスクの結果を出力し、最終的に非同期タスクの完了というメッセージを出力することがわかります。

Start executing async task...
Async task result: 42
Async task completed!

この例は、Rust でasyncとawaitキーワードを使用して非同期関数を作成する方法と、非同期関数内で非同期タスクを実行してその完了を待つ方法を示しています。

以下の例では、tokio ライブラリを使用して非同期 HTTP リクエストを実行し、レスポンス結果を出力します。

例2

// 必要な依存ライブラリをインポートする
use std::error::Error;
use tokio::runtime::Runtime;
use reqwest::get;

// HTTP GET リクエストを実行し、レスポンス結果を返す非同期関数
async fn fetch_url(url: &str) -> Result<String, Box<dyn Error>> {
    // reqwest を使用して非同期 HTTP GET リクエストを発行する
    let response = get(url).await?;
    let body = response.text().await?;
    Ok(body)
}

// 非同期タスク実行関数
async fn execute_async_task() -> Result<(), Box<dyn Error>> {
    // 非同期 HTTP リクエストを発行する
    let url = "https://jsonplaceholder.typicode.com/posts/1";
    let result = fetch_url(url).await?;
    // レスポンス結果を出力する
    println!("Response: {}", result);
    Ok(())
}

// メイン関数
fn main() {
    // 非同期ランタイムを作成する
    let rt = Runtime::new().unwrap();
    // 非同期ランタイム内で非同期タスクを実行する
    let result = rt.block_on(execute_async_task());
    // 非同期タスクの実行結果を処理する
    match result {
        Ok(_) => println!("Async task executed successfully!"),
        Err(e) => eprintln!("Error: {}", e),
    }
}

上記のコードでは、まず非同期タスクの実行と HTTP リクエストのために、tokio と reqwest ライブラリをインポートしました。次に、非同期の HTTP GET リクエストを実行し、レスポンス結果を返す非同期関数 fetch_url を定義しました。

次に、非同期タスク実行関数 execute_async_task を定義しました。この関数は非同期 HTTP リクエストを発行し、レスポンス結果を出力します。

最後に、main 関数内で tokio 非同期ランタイムを作成し、その中で非同期タスクを実行して、非同期タスクの実行結果を処理しました。

このプログラムを実行すると、非同期 HTTP リクエストのレスポンス結果が出力されることがわかります。この例では JSONPlaceholder の投稿データをリクエストし、その内容を出力しました。


非同期プログラミングの説明

async キーワード

async キーワードは非同期関数を定義するために使用されます。つまり、Future または impl Future 型を返す関数です。非同期関数は実行時に未完了の Future オブジェクトを返します。これはまだ完了していない計算または操作を表します。

非同期関数は、他の非同期操作の完了を待つために await 式を含めることができます。

例

async fn hello() -> String {
    "Hello, world!".to_string()
}

await キーワード

await キーワードは、非同期操作の完了を待ち、その結果を取得するために使用されます。

await 式は非同期関数または非同期ブロック内でのみ使用できます。これは現在の非同期関数の実行を一時停止し、待機中の Future が完了するのを待ってから、後続のコードを実行します。

例

async fn print_hello() {
    let result = hello().await;
    println!("{}", result);
}

非同期関数の戻り値

非同期関数の戻り値の型は通常、impl Future<Output = T>ここでTは非同期操作の結果型です。.await非同期関数の戻り値は Future であるため、

例

async fn add(a: i32, b: i32) -> i32 {
    a + b
}

非同期ブロック

非同期関数の定義に加えて、Rust は非同期ブロックの構文も提供します。これにより、同期コード内で非同期操作を使用できます。非同期ブロックはasync { }で構成され、その中には非同期関数の呼び出しとawait式を含めることができます。

例

async {
    let result1 = hello().await;
    let result2 = add(1, 2).await;
    println!("Result: {}, {}", result1, result2);
};

非同期タスクの実行

Rust では、非同期タスクは通常、実行コンテキスト内で実行する必要があり、tokio::main、async-stdのtask::block_on 或 futures::executor::block_onなどの関数を使用して非同期タスクを実行できます。これらの関数は非同期関数または非同期ブロックを受け取り、現在のスレッドまたは実行環境内で実行します。

例

use async_std::task;

fn main() {
    task::block_on(print_hello());
}

エラーハンドリング

awaitその後ろに?演算子を付けるとエラーを伝播できます。もしawaitの Future が完了したときにエラーを返した場合、そのエラーは呼び出し元に伝播されます。

例

async fn my_async_function() -> Result<(), MyError> {
    some_async_operation().await?;
    // some_async_operation がエラーを返した場合、エラーが伝播されます
}

非同期トレイトメソッド

Rust では、トレイトに対して非同期メソッドを定義できます。これにより、異なる型のオブジェクトに対して非同期操作を定義できます。

例

trait MyAsyncTrait {
    async fn async_method(&self) -> Result<(), MyError>;
}

impl MyAsyncTrait for MyType {
    async fn async_method(&self) -> Result<(), MyError> {
        // 非同期ロジック
    }
}

非同期コンテキスト

Rust では、非同期コードは通常、非同期ランタイム(Tokio や async-std など)内で実行されます。これらのランタイムは、非同期タスクをスケジュールして実行するメカニズムを提供します。

例

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

上記のコードでは、#[tokio::main]属性マクロは、main関数を非同期ランタイム内にラップします。

非同期マクロ

Rust はいくつかの非同期マクロを提供しています。たとえば、tokio::spawn、非同期ランタイム内で新しい非同期タスクを起動するために使用されます。

例

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        // 非同期ロジック
    });
    handle.await.unwrap();
}

非同期I/O

Rust の標準ライブラリは非同期 I/O 操作を提供しています。たとえば、tokio::fs::Fileとasync_std::fs::File。

例

use tokio::fs::File;
use tokio::io::{self, AsyncReadExt};

#[tokio::main]
async fn main() -> io::Result<()> {
    let mut file = File::open("file.txt").await?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).await?;
    println!("Contents: {}", contents);
    Ok(())
}

非同期チャネル

Rust の一部の非同期ランタイムは、非同期チャネル(tokio::sync::mpsc など)を提供しており、非同期タスク間でメッセージを渡すことができます。

例

use tokio::sync::mpsc;
use tokio::spawn;

#[tokio::main]
async fn main() {
    let (tx, mut rx) = mpsc::channel(32);

    let child = spawn(async move {
        let response = "Hello, world!".to_string();
        tx.send(response).await.unwrap();
    });

    let response = rx.recv().await.unwrap();
    println!("Received: {}", response);

    child.await.unwrap();
}

まとめ

Rust の非同期プログラミングモデル async/await は、非同期操作を処理するための簡潔で効率的な方法を提供します。

これにより、開発者はより自然で直感的な方法で非同期操作を処理でき、同時に Rust の安全性とパフォーマンスを維持できます。

async/await により、Rust は非同期プログラミングに対して第一級の言語サポートを提供し、効率的で読みやすい非同期プログラムの作成をより容易にします。

その他の拡張