Macro anony::try_join

source ·
try_join!() { /* proc-macro */ }
Available on crate feature future only.
Expand description

Returns a future that “joins” multiple futures that will be completed concurrently. May short-circuit.

It is similar to join!, except it resolves to “continue” value if all the futures resolve to “continue” value, and resolves to “break” value if one of the futures resolves to “break” value. The “continue” and the “break” value are dependent on the output type of all the futures.

Here’s a basic overview of possible return types and return values:

(F0::Output, F1::Output, ...)TryJoin::Output“Continue” value“Break” valueNote
(Option<T0>, Option<T1>, ...)Option<(T0, T1, ...)>SomeNone
(Result<T0, E>, Result<T1, E>, ...)Result<(T0, T1, ...), E>OkErrAll errors must exactly be the same
(ControlFlow<B, C0>, ControlFlow<B, C1>, ...)ControlFlow<B, (C0, C1, ...)>ContinueBreak

Formally, the trait bound is as the following, in term of Try and Residual traits:

impl<
        F0: Future,
        F1: Future,
        ...
        FN: Future,
        R: Residual<(
            <F0::Output as Try>::Output,
            <F1::Output as Try>::Output,
            ...
            <FN::Output as Try>::Output,
        )>,
    > Future for TryJoin<F0, F1, ..., FN, R>
where
    F0::Output: Try<Residual = R>,
    F1::Output: Try<Residual = R>,
    ...
    FN::Output: Try<Residual = R>,
{
    type Output = R::TryType;
}

It means that in theory, you can use Poll<Result<T, E>> and Poll<Option<Result<T, E>>> and mix up with other futures returning Result<T, E>! However, to prevent such mess, the two types are NOT allowed as of now. It may be lifted later when many unstable try_* methods/functions are stabilized.

If the standard library add more types implementing Try or Residual, this macro will NOT be aware of it. You can only be waiting till this crate is updated, or both traits are stabilized.

§DISCLAIMER

This macro does NOT use nightly or beta channel. It is usable on stable release.

§Possible differences from other implementations

  • try_join! returns an instance of an anonymous type implemented Future instead of requiring it to be inside an async. You will be warned if you neither .await, poll, nor return it.

  • input futures are required to implement IntoFuture, and their outputs can be more than just Result (see the first section above for the supported types).

  • the returned future (generally) has smaller size and is (generally) faster.

  • the returned future is Unpin if all of the input futures are Unpin.

§Examples

use anony::try_join;

let a = async { Some(1) };
let b = async { Some(2) };
let c = async { Some(3) };
assert_eq!(try_join!(a, b, c).await, Some((1, 2, 3)));

let a = async { Ok(4) };
let b = async { Err::<(), _>("5") };
assert_eq!(try_join!(a, b).await, Err("5"));

let a = async { Some("6") };
let b = async { None::<&str> };
let c = async { Some("7") };
assert_eq!(try_join!(a, b, c).await, None);

If you want to run a future (or more) while doing something else, this macro is a help! Note that you must put the “something else” after every other futures you want to run:

use anony::try_join;
use tokio::time::sleep;
use std::time::Duration;

async fn read_db() -> Result<String, Box<dyn Error>> {
    sleep(Duration::from_secs(1)).await;
    Ok("My secret".into())
}

let (secret_value, _) = try_join!(read_db(), async {
    // Your other tasks go here, maybe asynchronous or just blocking...
    let a = 1;
    let b = 2;
    assert_eq!(a + b, 3);
    Ok(())
}).await?;

assert_eq!(secret_value, "My secret");