1. 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 的完整链路。左侧是同步调用路径,右侧是异步调用路径,中间用颜色区分“未执行”与“已执行”两种状态。
关键观察点
- 调用不执行:
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 就绪时通知执行器重新调度,这是异步任务能够高效并发的基础。