踩坑最深的一次是异步闭包的生命周期。
当时用 tokio 写个高并发服务,spawn 出去的 task 里引用了外部变量。编译器死活不让过,报错信息一长串,核心就是 future 捕获的引用活得不够久。折腾半天才反应过来,async block 本质上是个状态机,它捕获的引用必须和整个 future 同生命周期,而不是只活到 await 那一下。
解法很直接:
- 能用
move 就把所有权交出去
- 需要共享就套
Arc
- 内部还要改就
Arc<Mutex<T>> 或者 Arc<RwLock<T>>
- 实在复杂,把数据提前 clone 一份塞进去
你提到 borrow checker 较劲是在挡 bug,这点我特别有体会。之前写过一篇帖子聊“Rust教我写更好的C”,就是这个意思。跟借用检查器死磕的过程,其实是在逼你把数据的流向、谁拥有谁、谁只是借来看看,全在脑子里理清楚。以前写 C 的时候这些全靠人脑记,项目一大就容易漏,现在编译器替你把这活干了。
补充一点关于零成本抽象。它在大多数场景下确实不收费,但有个地方得留神:动态分发(dyn Trait)。用了 trait object 就会引入虚表指针,多一次间接寻址。在对延迟极度敏感的代码路径上,这个开销是能测出来的。能静态分发(泛型单态化)的地方尽量别用动态分发。
另外你说类型系统是重构的安全网,这个体验太真实了。上个月给一个跑了快两年的老项目改了底层数据结构,本来做好了通宵的准备。结果改完定义,cargo check 跑一遍,顺着报错一个个修,全绿之后测试直接过。那种感觉就像拼乐高,接口对上了就不会散架。
prof_718 上次还跟我吐槽 Rust 编译慢,大项目增量编译确实要等一会。不过换个角度想,等编译的这几分钟去倒杯奶茶,总比半夜被叫起来查段错误强。oak__uk 之前分享的那个用 cargo-nextest 加速测试的方案也挺好用,可以试试。
你们平时处理跨线程共享状态,更倾向用 channel 还是直接上锁?