Calling an async fn creates a future. A small executor can poll an immediately-ready future to get the result.

Program

Play the program to choose a base value, create a future, and resolve it into points.

base
async_function.rs
Replay: real traced execution (multi-file project)
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

fn main() {
    let base = 8;
    let future = load_points(base);
    let points = block_on(future);
    println!("{points}");
}

async fn load_points(base: i32) -> i32 {
    base + 4
}

fn block_on<F: Future>(future: F) -> F::Output {
    let waker = noop_waker();
    let mut context = Context::from_waker(&waker);
    let mut future = pin!(future);
    loop {
        match future.as_mut().poll(&mut context) {
            Poll::Ready(value) => return value,
            Poll::Pending => {}
        }
    }
}

fn noop_waker() -> Waker {
    unsafe { Waker::from_raw(noop_raw_waker()) }
}

fn noop_raw_waker() -> RawWaker {
    RawWaker::new(std::ptr::null(), &VTABLE)
}

static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| noop_raw_waker(), |_| {}, |_| {}, |_| {});
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

fn main() {
    let base = 3;
    let future = load_points(base);
    let points = block_on(future);
    println!("{points}");
}

async fn load_points(base: i32) -> i32 {
    base + 4
}

fn block_on<F: Future>(future: F) -> F::Output {
    let waker = noop_waker();
    let mut context = Context::from_waker(&waker);
    let mut future = pin!(future);
    loop {
        match future.as_mut().poll(&mut context) {
            Poll::Ready(value) => return value,
            Poll::Pending => {}
        }
    }
}

fn noop_waker() -> Waker {
    unsafe { Waker::from_raw(noop_raw_waker()) }
}

fn noop_raw_waker() -> RawWaker {
    RawWaker::new(std::ptr::null(), &VTABLE)
}

static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| noop_raw_waker(), |_| {}, |_| {}, |_| {});
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};

fn main() {
    let base = 12;
    let future = load_points(base);
    let points = block_on(future);
    println!("{points}");
}

async fn load_points(base: i32) -> i32 {
    base + 4
}

fn block_on<F: Future>(future: F) -> F::Output {
    let waker = noop_waker();
    let mut context = Context::from_waker(&waker);
    let mut future = pin!(future);
    loop {
        match future.as_mut().poll(&mut context) {
            Poll::Ready(value) => return value,
            Poll::Pending => {}
        }
    }
}

fn noop_waker() -> Waker {
    unsafe { Waker::from_raw(noop_raw_waker()) }
}

fn noop_raw_waker() -> RawWaker {
    RawWaker::new(std::ptr::null(), &VTABLE)
}

static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| noop_raw_waker(), |_| {}, |_| {}, |_| {});
  1. base ← 8, future ← (empty)

    5fn main() {6    let bas→ 8e = 8; //@base=8, 3, 127    let futur→ (empty)e = load_points(bas8e);8    let points = block_on(futur(empty)e);9    println!("{points}");
  2. waker ← Waker { data: 0x0, vtable: ⟨addr A⟩ }, context ← Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }

    16fn block_on<F: Future>(future: F) -> F::Output {17    let wake→ Waker { data: 0x0, vtable: ⟨addr A⟩ }r = noop_waker();18    let mut contex→ Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }t = Context::from_waker(&wakeWaker { data: 0x0, vtable: ⟨addr A⟩ }r);19    let mut futur→ (empty)e = pin!(future);20    loop {
  3. fn load_points(base: i32) -> i32

    12async fn load_points(base: i32) -> i32 {13    bas8e + 414}
  4. points ← 12

    7    let future = load_points(base);8    let point→ 12s = block_on(futur(empty)e);9    println!("{points}");10}
    output12
  1. base ← 3, future ← (empty)

    5fn main() {6    let bas→ 3e = 3;7    let futur→ (empty)e = load_points(bas3e);8    let points = block_on(futur(empty)e);9    println!("{points}");
  2. waker ← Waker { data: 0x0, vtable: ⟨addr A⟩ }, context ← Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }

    16fn block_on<F: Future>(future: F) -> F::Output {17    let wake→ Waker { data: 0x0, vtable: ⟨addr A⟩ }r = noop_waker();18    let mut contex→ Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }t = Context::from_waker(&wakeWaker { data: 0x0, vtable: ⟨addr A⟩ }r);19    let mut futur→ (empty)e = pin!(future);20    loop {
  3. fn load_points(base: i32) -> i32

    12async fn load_points(base: i32) -> i32 {13    bas3e + 414}
  4. points ← 7

    7    let future = load_points(base);8    let point→ 7s = block_on(futur(empty)e);9    println!("{points}");10}
    output7
  1. base ← 12, future ← (empty)

    5fn main() {6    let bas→ 12e = 12;7    let futur→ (empty)e = load_points(bas12e);8    let points = block_on(futur(empty)e);9    println!("{points}");
  2. waker ← Waker { data: 0x0, vtable: ⟨addr A⟩ }, context ← Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }

    16fn block_on<F: Future>(future: F) -> F::Output {17    let wake→ Waker { data: 0x0, vtable: ⟨addr A⟩ }r = noop_waker();18    let mut contex→ Context { waker: Waker { data: 0x0, vtable: ⟨addr A⟩ } }t = Context::from_waker(&wakeWaker { data: 0x0, vtable: ⟨addr A⟩ }r);19    let mut futur→ (empty)e = pin!(future);20    loop {
  3. fn load_points(base: i32) -> i32

    12async fn load_points(base: i32) -> i32 {13    bas12e + 414}
  4. points ← 16

    7    let future = load_points(base);8    let point→ 16s = block_on(futur(empty)e);9    println!("{points}");10}
    output16
async fn Calling an `async fn` creates a future instead of running the body immediately.
Future A future represents work that can be polled until it returns `Ready`.
executor `block_on` is a tiny executor for these immediately-ready teaching examples.