Rust 异步编程

Rust 的异步和 JavaScript、Python 都不一样:它没有内置运行时。语言只提供了 Future 这个 trait 和 async/await 语法糖,真正的调度由社区 crate(最主流的是 Tokio)完成。这正是「零成本抽象」的体现——你不为用不到的调度器买单。

什么是 Future

Future 是一个「未来才会产出值」的计算:

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

// 一个最简单的 Future:等一秒后返回 42
struct HelloFuture {
    slept: bool,
}

impl Future for HelloFuture {
    type Output = u32;
    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<u32> {
        if self.slept {
            Poll::Ready(42)
        } else {
            self.slept = true;
            cx.waker().wake_by_ref(); // 告诉运行时:待会儿再问我
            Poll::Pending
        }
    }
}

关键在于 pollFuture 不会自己跑,是运行时反复来「戳」它。返回 Pending 表示还没好,返回 Ready 表示出结果了。

async/await 语法

手写 poll 太反人类,所以有了 async

async fn fetch_data() -> Result<String, reqwest::Error> {
    let resp = reqwest::get("https://example.com").await?;
    resp.text().await
}

#[tokio::main]
async fn main() {
    match fetch_data().await {
        Ok(body) => println!("拿到 {} 字节", body.len()),
        Err(e) => eprintln!("出错:{e}"),
    }
}

async fn 编译后就是一个返回 Future 的状态机;await 点上,状态机暂停、把控制权交还运行时。

Tokio 运行时

#[tokio::main] 帮你启动一个多线程调度器。并发跑多个任务用 tokio::join!spawn

let (a, b) = tokio::join!(fetch_data(), fetch_data());

三个常见坑

  1. 在异步里调阻塞函数(如 std::thread::sleep)会卡住整个线程——改用 tokio::time::sleep
  2. await 不加 .await——编译器不会报错,但 Future 永远不会执行。
  3. .await 持有 &mut 引用容易触发借用检查器的「终身追杀」,多用 Arc<Mutex<T>> 解耦。

异步 Rust 难,是因为它把并发的复杂性诚实地摆在你面前,而不是藏在运行时底下。熬过前两周,你会感谢它的严谨。