join!
The futures::join
macro makes it possible to wait for multiple different
futures to complete while executing them all concurrently.
join!
When performing multiple asynchronous operations, it's tempting to simply
.await
them in a series:
async fn get_book_and_music() -> (Book, Music) {
let book = get_book().await;
let music = get_music().await;
(book, music)
}
However, this will be slower than necessary, since it won't start trying to
get_music
until after get_book
has completed. In some other languages,
futures are ambiently run to completion, so two operations can be
run concurrently by first calling each async fn
to start the futures, and
then awaiting them both:
// 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)
}
However, Rust futures won't do any work until they're actively .await
ed.
This means that the two code snippets above will both run
book_future
and music_future
in series rather than running them
concurrently. To correctly run the two futures concurrently, use
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)
}
The value returned by join!
is a tuple containing the output of each
Future
passed in.
try_join!
For futures which return Result
, consider using try_join!
rather than
join!
. Since join!
only completes once all subfutures have completed,
it'll continue processing other futures even after one of its subfutures
has returned an Err
.
Unlike join!
, try_join!
will complete immediately if one of the subfutures
returns an error.
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)
}
Note that the futures passed to try_join!
must all have the same error type.
Consider using the .map_err(|e| ...)
and .err_into()
functions from
futures::future::TryFutureExt
to consolidate the error types:
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)
}