How to use "await?" inside vec.iter().map() in an async fn?

i have a vector i want to iterate over and use map to call an async function (see code below).
However, i keep getting compile error - please see fn sum below
How do i get the code to compile?

Code Sample:

#[derive(Debug)]
struct B { val: u32 }
impl B {
    pub async fn do_b(&self) -> Result<u32, anyhow::Error> { Ok(1) }
    pub fn do_c(&self) -> Result<u32, anyhow::Error> {Ok(2)}
}

// sum() is async, why can't i call await?
async fn sum(arr: Vec<B>) -> Result<u32, anyhow::Error> {
    // can't compile - Error A - fn sum is an async fn, why can't i call await?
    // error[E0728]: `await` is only allowed inside `async` functions and blocks
    let values: Vec<Result<u32, anyhow::Error>> = arr.iter().map(|b| b.do_b().await? ).collect();

    // can't compile - Error B - tried to wrap call in async block
    // error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
    let values: Vec<Result<u32, anyhow::Error>> = arr.iter().map(|b| async { b.do_b().await? } ).collect();

    // my sanity check -can compile
    let values: Vec<Result<u32, anyhow::Error>> = arr.iter().map(|b| b.do_c()).collect(); 

    B{val:0}.do_b().await?; // sanity check - can compile
    Ok(0)
}
Error-A:
error[E0728]: `await` is only allowed inside `async` functions and blocks
   |
41 |     let values: Vec<Result<u32, anyhow::Error>> = arr.iter().map(|b| b.do_b().await? ).collect(); // can't compile - Error A
   |                                                                  --- ^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks
   |                                                                  |
   |                                                                  this is not `async`


Error-B:
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `FromResidual`)
    |
42  |     let values: Vec<Result<u32, anyhow::Error>> = arr.iter().map(|b| async { b.do_b().await? } ).collect(); // can't compile - Error B
    |                                                                            ----------------^--
    |                                                                            |               |
    |                                                                            |               cannot use the `?` operator in an async block that returns `u32`
    |                                                                            this function should return `Result` or `Option` to accept `?`
    |
    = help: the trait `FromResidual<Result<Infallible, anyhow::Error>>` is not implemented for `u32`
note: required by `from_residual`
 
    |
339 |     fn from_residual(residual: R) -> Self;
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1 Like

Error A occurs because the await is in the closure, not in the outer function. The solution is to make the body of the closure async, as you have done, leading to error B.

Error B occurs because the output type of b.do_b().await? is u32, not Result, and it becomes the output of the async block because it is the last expression there. You could make it into a Result by wrapping it in Ok, but that would be redundant, so the better solution is to just remove the ?, since the await already produces a Result.

However, if you do that (playground), you will encounter a third error that indicates the iterator isn't doing what you probably expect it to do. The error is that the iterator actually produces Futures, so you can't collect into a vector of Results. That's because async builds a new Future, so the async-await in the map is just adding an unnecessary wrapping of the future already returned by do_b(), rather than actually running the Future.

What you probably want instead is something like futures::future::join_all(), which takes a collection of Futures and merges them into a single Future that collects their results, which you can then await to get the Vec you want.

4 Likes

If you have a small number of elements then join_all(iter.map(|x| async { can.await here }) will work.

If the number of elements is huge, then I suggest using a stream from iterator and buffer_unordered + collect to ensure too many of them aren't processed at the same time. It is a bit fiddly, since some stream methods expect returning async blocks, and some not.

4 Likes

thanks for the explanation

Thanks for the explanation and suggestions

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.