Here's a small example of an async function in Rust.

async fn data() -> u8 {
    0
}

async fn test() {
    println!("{}", data().await);
}

fn main() {
    dbg!(std::mem::size_of_val(&test()));
}

Let's run this program and see how big the test() future is.

[src/main.rs:10:5] std::mem::size_of_val(&test()) = 24

Here's the quiz: can we make the Future returned by test() smaller? I'll give you a moment to think about it.

Answer How is the `Future` the compiler is constructing for us laid out? We can use `gdb` to print out the type `rustc` is generating for us.
(gdb) ptype test()
type = enum shrink_future::test::{async_fn_env#0} {
  0,
  1,
  2,
  3{__0: &[&str], __awaitee: shrink_future::data::{async_fn_env#0}},
}

Since our test() future has one .await point, we need to store the future we're waiting on as well as any data used across the .await point. The println! macro is setting up a &[&str] for us which is accessed on both sides of the .await. This is unnecessary; if we .await the data() future first, and store it in a local variable before doing println!, then we'll only have to store the future we're waiting on.

async fn test() {
    let data = data().await;
    println!("{}", data);
}

Let's run our program again.

[src/main.rs:11:5] std::mem::size_of_val(&test()) = 2

Much better!