很多人用asyncRust一段时间后,都会有一种矛盾感:表面上:async/await用起来很顺实际上:一到性能、内存、卡顿、奇怪的borrow报错,就开始失控于是常见的评价是:“asyncRust太复杂了”但真正的问题不是“复杂”,而是:asyncRust
很多人用 async Rust 一段时间后,都会有一种矛盾感:
async/await 用起来很顺于是常见的评价是:
“async Rust 太复杂了”
但真正的问题不是“复杂”,而是:
async Rust 没帮你隐藏任何成本。

在不少语言/框架里,async 被潜移默化地理解成:
“写起来像同步,但跑起来很快”
Rust 不认这个说法。
在 Rust 的语义里:
async 在 Rust 中只做一件事:
把函数编译成一个可被暂停和恢复的状态机。
仅此而已。
你写下:
async fn fetch() -> Data {
let a = step1().await;
let b = step2(a).await;
step3(b)
}
编译器看到的不是“异步函数”,而是近似于:
enum FetchFuture {
Start,
WaitingStep1(Step1Future),
WaitingStep2(Step2Future),
Done,
}
并且:
await = 一个状态边界await 之前活着的变量 = Future 的字段👉 这直接决定了 async Rust 的内存和性能特征。
很多人第一次被 async Rust 折磨,通常是这类报错:
borrowed value does not live long enough cannot borrow across await
这不是编译器刁难你,而是状态机模型的必然结果。
await 可能暂停当前任务如果你在 await 前借了一个引用:
let x = &self.field;
foo().await;
use(x);
那就意味着:
self 内部的引用Rust 选择了最保守、也最安全的策略:
除非你能证明地址稳定,否则禁止。
Pin 出现的真正原因:不是为难你,而是救你Pin 是 async Rust 最容易被误解的概念之一。
你需要记住一句话:
Pin 不是“不能动”,而是“不能在你不知道的情况下被动”。
因为:
于是 Rust 设计了一个协议:
Pin<&mut T>Unpin这是一种显式的安全契约。
async 的性能瓶颈,几乎从来不在 await 本身。
真正关键的是:
async 任务 ≈ 一个被频繁 poll 的小状态机
Pending = “不行,等我被唤醒”大量细碎 async 任务 = 大量调度和唤醒。
很多 async 新手喜欢这样写:
tokio::spawn(async move {
do_something().await;
});
感觉很“并发”,很“异步”。
但在 Rust 里,这意味着:
spawn 不是免费午餐。
经验法则:
一个非常反直觉但极其重要的事实:
async 函数里的局部变量,几乎都活在堆上。
因为:
这意味着:
于是你会看到成熟的 async Rust 代码中:
drop这是为状态机瘦身。
很多人把 Tokio 当成“异步库”。
更准确的说法是:
Tokio 是一个高性能的任务调度器 + IO 反应堆。
它解决的不是:
而是:
一旦你理解 async 的本质是状态机,Tokio 的设计就会变得非常合理、甚至不可替代。
如果你只记住这一篇的一句话,那应该是:
async Rust = 显式状态机 + 显式调度成本 + 显式内存模型
它不帮你:
但作为回报,它给你:
很多语言的 async,是“应用级 async”: 方便、好用、但代价模糊。
Rust 的 async,是“系统级 async”:
这也是为什么:
一旦你真正理解 async Rust,你会发现它反而很诚实。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!