You are correct; my statement was too strong and I have corrected it.
But regarding the memory usage characteristics of async
blocks, it’s worth noting that in the current implementation, all variables that are in scope of the await
, and haven't been moved out of, get stored, even when not doing so is an obvious optimization. For example:
pub async fn example() {
other().await;
let x = 1;
println!("{x}");
other().await;
}
pub async fn other() {}
$ cargo +nightly rustc --lib -- -Zprint-type-sizes
...
print-type-size type: `{async fn body of example()}`: 8 bytes, alignment: 4 bytes
print-type-size discriminant: 1 bytes
print-type-size variant `Unresumed`: 0 bytes
print-type-size variant `Suspend0`: 1 bytes
print-type-size local `.__awaitee`: 1 bytes, type: {async fn body of other()}
print-type-size variant `Suspend1`: 7 bytes
print-type-size local `.__awaitee`: 1 bytes, type: {async fn body of other()}
print-type-size padding: 2 bytes
print-type-size local `.x`: 4 bytes, alignment: 4 bytes
print-type-size variant `Returned`: 0 bytes
print-type-size variant `Panicked`: 0 bytes
...
There is no need to store the integer x
— in particular, it has no destructor, so it is not needed at the end of the block — but it is stored anyway, so if you want a memory-efficient async fn
, you may need to add blocks so that no unnecessary variables are in scope at the await
points:
pub async fn example() {
other().await;
{
let x = 1;
println!("{x}");
}
other().await;
}
$ cargo +nightly rustc --lib -- -Zprint-type-sizes
...
print-type-size type: `{async fn body of example()}`: 2 bytes, alignment: 1 bytes
print-type-size discriminant: 1 bytes
print-type-size variant `Unresumed`: 0 bytes
print-type-size variant `Suspend0`: 1 bytes
print-type-size local `.__awaitee`: 1 bytes, type: {async fn body of other()}
print-type-size variant `Suspend1`: 1 bytes
print-type-size local `.__awaitee`: 1 bytes, type: {async fn body of other()}
print-type-size variant `Returned`: 0 bytes
print-type-size variant `Panicked`: 0 bytes
...
(I had to learn about this behavior when trying to make some async code fit on a machine with a small stack; even though these Future
s are conceptually moving data off the stack, they still have to be returned by value from their async fn
s, and the total size can quickly add up when those futures manipulate large values and call other futures that do the same.)