join!
futures::join マクロを使うと、複数の異なる Future をすべて並行に実行しながら、それらが完了するのを待てます。
join!
複数の非同期操作を実行するとき、単純にそれらを順番に .await したくなるかもしれません。
async fn get_book_and_music() -> (Book, Music) {
let book = get_book().await;
let music = get_music().await;
(book, music)
}
しかし、これは必要以上に遅くなります。なぜなら、get_book が完了するまで get_music の実行を試み始めないためです。他の言語では、Future が暗黙的に完了まで実行されることがあるため、まず各 async fn を呼び出して Future を開始し、その後で両方を await することで、2 つの操作を並行に実行できます。
// WRONG -- don't do this
async fn get_book_and_music() -> (Book, Music) {
let book_future = get_book();
let music_future = get_music();
(book_future.await, music_future.await)
}
しかし、Rust の Future は能動的に .await されるまで何も処理しません。つまり、上記の 2 つのコードスニペットはどちらも、book_future と music_future を並行に実行するのではなく、順番に実行します。2 つの Future を正しく並行に実行するには、futures::join! を使用します。
use futures::join;
async fn get_book_and_music() -> (Book, Music) {
let book_fut = get_book();
let music_fut = get_music();
join!(book_fut, music_fut)
}
join! が返す値は、渡された各 Future の出力を含むタプルです。
try_join!
Result を返す Future では、join! ではなく try_join! の使用を検討してください。join! はすべてのサブ Future が完了して初めて完了するため、サブ Future の 1 つが Err を返した後でも、他の Future の処理を継続します。
join! とは異なり、try_join! はサブ Future の 1 つがエラーを返すと即座に完了します。
use futures::try_join;
async fn get_book() -> Result<Book, String> { /* ... */ Ok(Book) }
async fn get_music() -> Result<Music, String> { /* ... */ Ok(Music) }
async fn get_book_and_music() -> Result<(Book, Music), String> {
let book_fut = get_book();
let music_fut = get_music();
try_join!(book_fut, music_fut)
}
try_join! に渡される Future は、すべて同じエラー型でなければならないことに注意してください。エラー型を統合するには、futures::future::TryFutureExt の .map_err(|e| ...) 関数と .err_into() 関数の使用を検討してください。
use futures::{
future::TryFutureExt,
try_join,
};
async fn get_book() -> Result<Book, ()> { /* ... */ Ok(Book) }
async fn get_music() -> Result<Music, String> { /* ... */ Ok(Music) }
async fn get_book_and_music() -> Result<(Book, Music), String> {
let book_fut = get_book().map_err(|()| "Unable to get book".to_string());
let music_fut = get_music();
try_join!(book_fut, music_fut)
}