Waiting after async move

Hi
I have the following code

#[tokio::main]
async fn main() -> io::Result<()> {
.....
let handles = generate_data();
for handle in handles {
        handle.await.unwrap();
 }
}

fn generate_data() ->
Result<Vec<tokio::task::JoinHandle<()>> , Box<dyn Error>> {
....
let mut handles = vec![];
for i in 1..n {
 let task = tokio::spawn(async move{
            if let Err(e) = process().await {
                error!("failed to spawn ; error = {}", e);
            }
        });
 handles.push(task);
}
}
Ok(handles)
}

But I get the following error on compilation .
How do I wait for the aync tasks to complete ?

error[E0277]: `Vec<tokio::task::JoinHandle<()>>` is not a future
   --> src/serve-data.rs:109:9
    |
109 |         handle.await.unwrap();
    |         ^^^^^^^^^^^^ `Vec<tokio::task::JoinHandle<()>>` is not a future
    |
    = help: the trait `futures::Future` is not implemented for `Vec<tokio::task::JoinHandle<()>>`
    = note: required by `futures::Future::poll`

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0277`.

Your code snippet is very incomplete and hard to follow, but my guess is that you have a double vector?

In any case, when you are spawning them, you should not be using join_all. You can simply loop:

for handle in handles {
    handle.await.unwrap();
}

This works when using tokio::spawn because the tokio::spawn call creates a background task that starts running immediately — it does not wait for the .await until it starts running.

Thanks . I have updated the code snippet in the original post. Posting here again .
I made the suggestion to do a wait on the Joinhandle
I still get the error about JoinHandle not being a future.

#[tokio::main]
async fn main() -> io::Result<()> {
.....
let handles = generate_data();
for handle in handles {
        handle.await.unwrap();
 }
}

fn generate_data() ->
Result<Vec<tokio::task::JoinHandle<()>> , Box<dyn Error>> {
....
let mut handles = vec![];
for i in 1..n {
 let task = tokio::spawn(async move{
            if let Err(e) = process().await {
                error!("failed to spawn ; error = {}", e);
            }
        });
 handles.push(task);
}
}
Ok(handles)
}

But I get the following error on compilation .
How do I wait for the aync tasks to complete ?

error[E0277]: `Vec<tokio::task::JoinHandle<()>>` is not a future
   --> src/serve-data.rs:109:9
    |
109 |         handle.await.unwrap();
    |         ^^^^^^^^^^^^ `Vec<tokio::task::JoinHandle<()>>` is not a future
    |
    = help: the trait `futures::Future` is not implemented for `Vec<tokio::task::JoinHandle<()>>`
    = note: required by `futures::Future::poll`

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0277`.

After cleaning the code up and running rustfmt, I got this (playground:

use std::error::Error;

#[tokio::main]
async fn main() -> tokio::io::Result<()> {
    let handles = generate_data();
    for handle in handles {
        handle.await.unwrap();
    }
    Ok(())
}

fn generate_data() -> Result<Vec<tokio::task::JoinHandle<()>>, Box<dyn Error>> {
    let mut handles = vec![];
    for i in 1..10 {
        let task = tokio::spawn(async move {});
        handles.push(task);
    }
    Ok(handles)
}

The error, indeed, is the one you've got.

Looks like you're currently expecting handle inside the loop to be JoinHandle<()>, but it somehow ends up being Vec<JoinHandle<()>>. Well, let's check if this is really the case:

#[tokio::main]
async fn main() -> tokio::io::Result<()> {
    let handles = generate_data();
    for handle in handles {
+       let _: tokio::task::JoinHandle<()> = handle;
        handle.await.unwrap();
    }
    Ok(())
}

The error seems to match our previous observations:

error[E0308]: mismatched types
 --> src/main.rs:7:46
  |
7 |         let _: tokio::task::JoinHandle<()> = handle;
  |                ---------------------------   ^^^^^^ expected struct `tokio::task::JoinHandle`, found struct `Vec`
  |                |
  |                expected due to this
  |
  = note: expected struct `tokio::task::JoinHandle<_>`
             found struct `Vec<tokio::task::JoinHandle<_>>`

OK, so the for loop really iterates over Vecs, not JoinHandles. But why would it do this, if handles is a Vec itself? Let's check if it really is...

#[tokio::main]
async fn main() -> tokio::io::Result<()> {
-   let handles = generate_data();
+   let handles: Vec<tokio::task::JoinHandle<()>> = generate_data();
    for handle in handles {
        handle.await.unwrap();
    }
    Ok(())
}

Another error!

error[E0308]: mismatched types
 --> src/main.rs:5:53
  |
5 |     let handles: Vec<tokio::task::JoinHandle<()>> = generate_data();
  |                  --------------------------------   ^^^^^^^^^^^^^^^ expected struct `Vec`, found enum `Result`
  |                  |
  |                  expected due to this
  |
  = note: expected struct `Vec<_>`
               found enum `Result<Vec<_>, Box<(dyn std::error::Error + 'static)>>`

Of course! The generate_data function is declared to return Result, not the Vec.

But now, why do the for handle in handles ever work, if handles is not a Vec? Now, to understand this, we should know how for loop works at all. Checking in the Rust reference, we see that this for loop is essentially syntactic sugar for the following (simplified a little for more readability):

let handles = IntoIterator::into_iter(handles);
loop {
    match Iterator::next(&mut handles) {
        Some(handle) => handle.await.unwrap(),
        None => break,
    }
}

Key point here is that for loop can use everything that implements the IntoIterator trait. Now, what about Result? Does it implement IntoIterator?

Yes, it does! And by checking the source code (impl IntoIterator and the corresponding impl Iterator), we can see that the implementation is fairly simple:

  • for Result::Ok(inner), it works like std::iter::once(inner);
  • for Result::Err(_), it works like an empty iterator.

That is, when you used for handle in handles, you've indeed "iterated" over a single value, which is a Vec.


Now, what can you do here? Well, you have to somehow deal with the error case - if the function returns Result, you can't simply use it as if it was successful.
The simplest case, as always, is just to unwrap() the return value:

#[tokio::main]
async fn main() -> tokio::io::Result<()> {
-   let handles = generate_data();
+   let handles = generate_data().unwrap();
    for handle in handles {
        handle.await.unwrap();
    }
    Ok(())
}

But, since your main function is already returning Result, you might want to propagate it outside, so that the program exits correctly without panic. For this you can try to use the question mark operator, but compiler will be unhappy again:

error[E0277]: `?` couldn't convert the error to `std::io::Error`
   --> src/main.rs:5:34
    |
5   |     let handles = generate_data()?;
    |                                  ^ the trait `From<Box<dyn std::error::Error>>` is not implemented for `std::io::Error`
    |
    = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    = help: the following implementations were found:
              <std::io::Error as From<Elapsed>>
              <std::io::Error as From<ErrorKind>>
              <std::io::Error as From<IntoInnerError<W>>>
              <std::io::Error as From<JoinError>>
              <std::io::Error as From<NulError>>
    = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, Box<dyn std::error::Error>>>` for `Result<(), std::io::Error>`
note: required by `from_residual`

The error is long and somewhat complicated, but the gist of it is simple: we can't convert arbitrary Box<dyn Error> into io::Error.
Fortunately, since after main the error will be processed by Rust runtime, we don't have to use io::Error explicitly at all. Just return the same Box<dyn Error>:

#[tokio::main]
-async fn main() -> tokio::io::Result<()> {
+async fn main() -> Result<(), Box<dyn Error>> {
-   let handles = generate_data();
+   let handles = generate_data()?;
    for handle in handles {
        handle.await.unwrap();
    }
    Ok(())
}

And now it compiles.

4 Likes

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.