What is the purpose of the additional outer async block

Viewed 109

In a lot of SO questions and also in a lot of external, reputable resources, such as the tokio tutorial I've seen people use an additional async block such as:

tokio::spawn(async move {
    process(socket).await;
});

instead of doing just

tokio::spawn(process(socket));

Both resolve to some Future and spawn() expects

pub fn spawn<T>(future: T) ....
where T: Future + ....

so I cannot understand the need for the additional async {} block.

Is it really necessary ? And is there any difference besides being a "visual marking" that this is an async code?

1 Answers

These are slightly different, depending on the implementation of process.

If process is defined like this:

fn process(socket: Socket) -> impl Future {
    // non-async stuff
    println!("non-async stuff");

    // async stuff
    async {
        println!("async stuff");
    }
}

Then invoking it with tokio::spawn(process(socket)); will execute the first line immediately and only the remaining sub-task, which prints "async stuff", will be scheduled.

If it defined like this:

async fn process(socket: Socket) {
    // non-async stuff
    println!("non-async stuff");

    // async stuff
    async {
        println!("async stuff");
    }.await;
}

Then tokio::spawn(process(socket)); will initally do nothing and will print both "non-async stuff" and "async stuff" when the scheduled task is executed.

Wrapping the call in a extra async block will make the two versions behave the same way. Whether or not this is the reason for the extra async block or if it is "needed" in the examples that you've seen is a question for the authors of those examples.

Related