直接回答async fn 返回一个实现了 Future 的匿名类型,编译器把函数体重写为状态机:每个 .await 是一个状态边界,跨 await 存活的局部变量被提升为该类型的字段——这决定了 async 值的大小,也可能产生自引用(字段间互相引用),这正是 Pin 存在的原因。

展开解析:执行器持有 Future 并调用 poll(cx):状态机运行到下一个 await,若内部 future 返回 Poll::Pending,本任务同样返回 Pending,并通过 Waker 注册唤醒回调;事件就绪后再次 poll,从断点继续。因此 await 是协作式、非抢占的让出点,不阻塞线程。几个推论:Future 是惰性的,不 poll 就不执行(与 JavaScript Promise 创建即启动不同);tokio::spawn 的任务会在工作窃取调度器间迁移,要求 Send + 'static,跨 await 持有 Rc 会编译失败;跨 await 持锁要用 tokio::sync::Mutex

async fn fetch(url: &str) -> String {
    let resp = client().get(url).send().await; // 状态边界 1
    resp.text().await                          // 状态边界 2
}

追问方向:为什么自引用 Future 必须 Pin?poll_fnBox::pin 的角色?trait 里的 async fn 为什么过去需要 async_trait 宏? (约 590 字)