首页 / Rust 入门教程 / 异步编程基础

Rust 入门教程

异步编程基础

本教程共 78 篇 · 第 71 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

RustRust 入门教程异步asyncawaitFuture2024 Edition

本节目标:理解 async/await 是怎么回事——Future 是惰性任务、.await 是让出线程而非阻塞线程,分清异步和多线程各自的适用场景,并知道 2024 Edition 为 async 带来的小变化。

如果说前面几章的线程是”一个任务占一个工人”,那异步就是”一个工人同时照看很多任务,谁闲着就干谁”。这一章我们进入 Rust 的异步世界,先打基础概念,运行时要等下一章再用 tokio。

1-1 为什么需要异步

并发有几种常见模型:操作系统线程最简单直观,但创建和切换成本高;事件驱动(回调)性能好,却容易陷入”回调地狱”;协程(如 Go)用起来舒服但抽象层次太高。Rust 选了 async/await:性能好、能贴近底层,写法又和同步代码差不多。

最适合异步的场景是 IO 密集型任务:一个 web 服务器同时要伺候成千上万个连接,每个连接大部分时间在等网络数据,真正计算的时间极少。如果给每个连接开一个线程,操作系统线程数很快见顶,上下文切换也拖垮性能。异步的做法是:一个任务在等 IO 时,立刻把控制权让出来,工人去干另一个任务;等 IO 好了再回来。切换只发生在内存里,比线程切换便宜得多。

Note

粗略对比:创建一个 async 任务约 0.3 微秒,切换约 0.2 微秒;而创建一个线程约 17 微秒,切换约 1.7 微秒。IO 密集场景下,异步的省电是实打实的。

1-2 Future 是惰性的

Rust 里异步的核心是 Future(未来)。你可以把它理解成”一个将来某个时刻才会真正跑完的任务计划”。

关键点:Future 是惰性的。光创建一个 Future,它不会自己跑。必须有人去”轮询”(poll)它,它才开始执行;遇到阻塞(比如等 IO),它就让出控制权,等下次被唤醒再继续,直到完成。这点跟 JavaScript 的 Promise 不同——Rust 的 Future 不主动执行,没人 poll 就一直躺着。

async fnasync { } 就能造出一个返回 Future 的东西:

async fn foo() -> u8 {
    5
}

fn bar() -> impl Future<Output = u8> {
    async {
        let x: u8 = foo().await;
        x + 5
    }
}

foo() 返回的是 Future<Output = u8>,意思是这个 Future 跑完后会给你一个 u8async 语句块同理。注意:Output 就是 Future 完成时产出的类型。

1-3 .await 让出线程,不是阻塞线程

.await 是驱动 Future 前进的方式,也是异步和同步写法最像的地方。但有个本质区别要刻进脑子:

.await 遇到阻塞时,是”让出当前线程的控制权”,而不是”阻塞当前线程”。

什么意思?假设线程上有两个 Future 在跑。Future A 执行到 .await 卡在等 IO,它不会把整个线程堵死,而是把线程让出来;于是线程转头去执行 Future B。等 A 等的 IO 好了,A 再被唤醒,拿回线程接着跑。一个线程就这样在多个任务之间来回切,实现了并发。

对比一下:block_on(future) 是”硬等”——它会真的阻塞当前线程,直到 Future 跑完。所以 .await 是异步里最常用的推进方式,而 block_on 更像是一个把异步结果”同步地”逼出来的入口(运行时的执行器常用它)。

use futures::executor::block_on;

async fn hello() {
    println!("你好,世界!");
}

fn main() {
    let future = hello(); // 只创建 Future,不打印任何东西
    block_on(future);     // 真正跑起来,才打印
}

如果只写 hello(); 而不 block_on.await,编译器会警告你:Future 创建了却没被使用,什么都不会发生。

1-4 用 .await 串起多个异步调用

async fn 里调用另一个异步函数,记得加 .await,否则那个 Future 永远不会执行:

use futures::executor::block_on;

async fn learn_song() -> String {
    String::from("《菊花台》")
}

async fn sing_song(song: String) {
    println!("献上一首 {}", song);
}

async fn dance() {
    println!("身体动了起来");
}

async fn learn_and_sing() {
    let song = learn_song().await; // 等学歌完成
    sing_song(song).await;         // 再等唱歌完成
}

fn main() {
    block_on(async {
        let f1 = learn_and_sing();
        let f2 = dance();
        futures::join!(f1, f2); // 两个 Future 并发跑
    });
}

这里”学歌→唱歌”有先后顺序,但这两件事都能和”跳舞”同时进行。futures::join!learn_and_singdance 并发:谁卡在 .await,就把线程让给另一个。用同步的 block_on 一个一个等,反而失去了并发的意义。join! 来自 futures 这个官方维护的包(在 Cargo.toml 里加 futures = "0.3")。

1-5 async 与多线程怎么选

两者都能并发,但不能互通,切换要重写代码,所以一开始就要选对:

  • 有大量 IO 任务要并发(web 服务、数据库连接):选 async。
  • 有部分 IO 任务、想省线程开销:可用多线程 + 线程池。
  • 大量 CPU 密集任务要并行(并行计算):选多线程,线程数约等于 CPU 核心数。
  • 无所谓、图简单:统一选多线程,代码直观得多。

async 也有代价:编译器会为每个 async 函数生成状态机,打包进运行时,导致编译出的二进制体积明显变大;而且 async 里生命周期、Pin 这些进阶概念更容易冒出来,学习曲线比线程陡。所以别盲目追异步,“简单够用”往往更重要。

1-6 2024 Edition 的兼容性说明

我们全书以 Rust 1.97.1 / 2024 Edition 为基线。对 async 而言,好消息是:基础的 async/await/Future 用法在 2024 Edition 下完全一致,没有破坏性改动,你前面学的写法照常工作。

2024 Edition 给 async 带来的主要便利是(来源:Rust 官方博客 2025-02-20 宣布 Rust 1.85.0 暨 2024 Edition 稳定):

  • FutureIntoFuture 被加进了 prelude。这意味着你不再需要到处写 use std::future::Future;,直接用 Future 这个名字即可,更接近其它语言的心智。
  • AsyncFnAsyncFnMutAsyncFnOnce 等异步 trait 得到稳定,异步闭包(async closure)也被稳定,写”像函数一样的异步块”更顺手。
  • 闭包对 Future 的捕获更精细了:2021 Edition 里有时要被迫写 async move {},2024 Edition 下很多情况不再需要。
Tip

如果你看到老教程里第一行就有 use std::future::Future;,别奇怪——那是 2021 Edition 或更早的写法。在 2024 Edition 项目里,这行可以省掉。但写上也不会报错,兼容性好。

需要提醒一点:async 运行时(如 tokio)和 async 代码本身有部分仍在演进,不同运行时(tokio 与 async-std)之间不完全互通。不过日常用成熟运行时,足够稳定。下一章我们就用社区最主流的 tokio 把异步跑起来。

1-7 Future 的底层一瞥

好奇的话,可以看一眼 Future 这个 trait 长什么样:

trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

poll 就是”推进一下”的意思,执行器反复调用它;返回 Poll::Pending 表示还没完、让出控制权,返回 Poll::Ready(val) 表示完成、给出结果。编译器把你写的 async fn 自动展开成实现了这个 trait 的状态机——每次 .await 都是状态机里的一个暂停点。你在第 74 章之后学到的很多”魔法”,底层都依赖于此。

日常写异步不必手搓 poll,tokio 这样的运行时替你调度。但知道它存在,遇到”为什么我的 Future 不执行""为什么状态机这么大”这类高级问题时,你就不会慌。理解底层,是为了在必要时能往下钻,而不是每天钻。

1-8 小结

异步的核心是 Future 惰性 + .await 让出线程。它用极少资源支撑海量并发任务,特别适合 IO 密集场景;CPU 密集任务还是多线程更合适。把 Future 纳入 prelude 确实是 2024 Edition 的改动;但「异步 trait(async fn in trait)」早在 Rust 1.75 就已稳定,并非 2024 Edition 的功劳——两者不是一回事。对你来说,基础用法都不变。光有 Future 还跑不起来,需要一个执行器去 poll 它——下章 tokio 登场。