In this Tokio tutorial, it has the code:
tokio::spawn(async move {
process(socket).await;
});
I can't figure out why async move {}
block is used here. To my understanding so far it is unnecessary. The following works just fine and is more concise and direct:
tokio::spawn(process(socket));
So, my questions are
async
block necessary in this code?async
block add an extra layer of indirection, likely to cause a tiny-bit of drop in the performance?
- Is the async block necessary in this code?
Most of the time, no; but see the next answer.
- If so, what difference does it make?
In case process()
is defined as async fn
, nothing. Some people prefer one form, but this is subjective.
However, if process()
is defined as a regular function that returns a Future
, there is a difference: with an async
block, all of process()
is executed in the newly spawned task, while without it, the call to process()
is executed in the parent task, and only the future it returns is executed in the spawned task.
This can be important, if, for example, the future that process()
returns is not 'static
(or Send
), in which case the call without the async
block will fail. Or if process()
itself may be expensive, and we want it to execute in another task, and not just the future it returns.
As a note, there is a similar distinction when calling Runtime::block_on()
, but there the distinction is even more important: without async
block, the code in process()
executes outside of the async runtime. That means that calls to functions that require an active runtime (e.g. tokio::spawn()
) will fail.
- Without compiler optimization, wouldn't the async block add an extra layer of indirection, likely to cause a tiny-bit of drop in the performance?
I don't know the exact implementation details of generators (and async functions and blocks, which are implemented on top of generators), but this is unlikely. Async blocks in Rust don't allocate, and I don't think that using an async
block that only forwards will incur any overhead.