1. async/await 基础

async/await 基础:异步函数定义与调用 学习卡片

异步函数与同步函数的调用差异

在 Rust 中,async fn 被调用时并不会立即执行函数体,而是返回一个实现了 Future trait 的匿名类型。这个 Future 处于“未启动”状态,只有被 .await 或交给执行器 poll 时才会开始推进。下面的例子定义了两个异步函数,并对比同步调用与异步调用的行为差异。

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

// 一个简单的异步函数:返回字符串
async fn fetch_name() -> String {
    println!("[fetch_name] 开始执行");
    "Rust".to_string()
}

// 另一个异步函数:调用上面的异步函数
async fn greet() -> String {
    println!("[greet] 准备调用 fetch_name");
    let name = fetch_name().await;   // 关键:.await 驱动 Future 执行
    println!("[greet] 拿到 name = {}", name);
    format!("Hello, {}!", name)
}

fn main() {
    // 1. 调用 async fn 不会执行函数体,只得到一个 Future
    let fut = greet();
    println!("调用 greet() 后,函数体尚未执行");

    // 2. 手动轮询这个 Future,观察惰性执行
    let mut fut = Box::pin(fut);
    let waker = noop_waker();
    let mut cx = Context::from_waker(&waker);

    match fut.as_mut().poll(&mut cx) {
        Poll::Ready(v) => println!("Future 完成,结果 = {}", v),
        Poll::Pending => println!("Future 尚未完成(本例不会发生)"),
    }
}

// 一个不唤醒任何任务的 waker,仅用于演示
fn noop_waker() -> std::task::Waker {
    use std::task::{RawWaker, RawWakerVTable, Waker};
    fn clone(_: *const ()) -> RawWaker { RawWaker::new(std::ptr::null(), &VTABLE) }
    fn wake(_: *const ()) {}
    fn wake_by_ref(_: *const ()) {}
    fn drop(_: *const ()) {}
    static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
    unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &VTABLE)) }
}
运行这段代码会先打印“调用 greet() 后,函数体尚未执行”,然后才打印 [greet] 和 [fetch_name] 中的内容。这直观地证明了 async fn 的惰性:调用不等于执行。

Future 惰性执行与 .await 驱动关系

下图展示了从调用 greet() 到最终得到 String 的完整链路。左侧是同步调用路径,右侧是异步调用路径,中间用颜色区分“未执行”与“已执行”两种状态。

async fn 调用与 .await 驱动链路 同步函数调用 fn sync_greet() -> String 调用即执行函数体 执行函数体 立即返回 String 得到结果 String "Hello, Rust!" 调用 → 执行 → 返回 一步完成,无中间状态 异步函数调用 async fn greet() -> impl Future 调用仅创建 Future,不执行 Future 处于未启动状态 函数体尚未执行 .await / poll 驱动执行 fetch_name().await 开始推进 Future 完成,返回 String "Hello, Rust!" 调用 → 创建 Future → .await 驱动 → 完成 对比

关键观察点

  • 调用不执行:let fut = greet(); 之后,greet 函数体内的 println! 不会输出,因为 Future 尚未被轮询。
  • .await 是驱动点:在 greet 内部,fetch_name().await 会先创建 fetch_name 的 Future,然后通过 .await 将其推进到完成,拿到 String 后继续执行后续代码。
  • 同步 vs 异步:同步函数调用后立即执行并返回结果;异步函数调用后返回一个“待执行”的 Future,需要显式驱动(.await 或执行器 poll)才会运行。
  • 手动 poll 验证:示例中通过 Box::pin 和自定义 noop_waker 手动轮询 Future,可以清晰看到 Poll::Ready 返回结果,证明 Future 确实被惰性执行。
本例中的 noop_waker 仅用于教学演示,它不会真正唤醒任务。在实际异步运行时(如 tokio)中,waker 负责在 Future 就绪时通知执行器重新调度,这是异步任务能够高效并发的基础。