Size of `async fn` futures
⚓ Rust 📅 2026-01-16 👤 surdeus 👁️ 8Background: I'm making a testing instrumentation crate for testing, and I'm wrapping Futures in functions that monitor their execution, and I want the wrapper function to be zero-cost when instrumentation is disabled.
I've noticed that Futures produced by async fns take up more space than I expected, and their size grows exponentially rather than linearly in their nesting depth.
I've ran this code:
async fn foo(inner: impl Future<Output = ()>) {
inner.await;
}
fn bar(inner: impl Future<Output = ()>) -> impl Future<Output=()> {
Bar { inner }
}
struct Bar<F> {
inner: F
}
impl<F: Future> Future for Bar<F> {
type Output = F::Output;
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context) -> std::task::Poll<Self::Output> {
let inner = unsafe { std::pin::Pin::new_unchecked(&mut self.get_unchecked_mut().inner) };
inner.poll(cx)
}
}
fn main() {
println!("sizeof async{{}} = {}", std::mem::size_of_val(&async{}));
println!("sizeof foo(async{{}}) = {}", std::mem::size_of_val(&foo(async{})));
println!("sizeof foo(foo(async{{}})) = {}", std::mem::size_of_val(&foo(foo(async{}))));
println!("sizeof foo(foo(foo(async{{}}))) = {}", std::mem::size_of_val(&foo(foo(foo(async{})))));
println!("sizeof foo(foo(foo(foo(async{{}})))) = {}", std::mem::size_of_val(&foo(foo(foo(foo(async{}))))));
println!("sizeof bar(async{{}}) = {}", std::mem::size_of_val(&bar(async{})));
println!("sizeof bar(bar(async{{}})) = {}", std::mem::size_of_val(&bar(bar(async{}))));
println!("sizeof bar(bar(bar(async{{}}))) = {}", std::mem::size_of_val(&bar(bar(bar(async{})))));
println!("sizeof bar(bar(bar(bar(async{{}})))) = {}", std::mem::size_of_val(&bar(bar(bar(bar(async{}))))));
}
and it prints the following (it's the same with and without optimizations):
sizeof async{} = 1
sizeof foo(async{}) = 3
sizeof foo(foo(async{})) = 7
sizeof foo(foo(foo(async{}))) = 15
sizeof foo(foo(foo(foo(async{})))) = 31
sizeof bar(async{}) = 1
sizeof bar(bar(async{})) = 1
sizeof bar(bar(bar(async{}))) = 1
sizeof bar(bar(bar(bar(async{})))) = 1
Why are futures produced by async fn so much larger than the equivalent manual implementation of Future?
Am I missing some optimization opportunities here?
Can I reduce the size of results of async fn without implementing Futures manually?
2 posts - 2 participants
🏷️ Rust_feed