关于 Rust 的一些思考
1. Rust 最大的四个优势
- Cargo如果你让一个 C++ 程序员第一次接触 Cargo,他一定会爱上它。这种开箱即用的包管理和构建工具,在 C++ 生态中完全找不到。
- 友好的错误提示Rust 的编译器错误信息非常清晰、实用,比起 C++ 那堆抽象的模板报错好上太多。
- 函数是第一公民Rust 中函数可以作为参数、返回值,支持闭包,函数式编程体验流畅。
- 轻量级 OOP
Rust 保留了面向对象的一些影子,但做了很好的解耦。
- 封装 :
struct依旧保持了抽象的能力,聚焦于数据结构的抽象。同时mod依旧有访问控制 - 多态 : 基于trait对象也能实现动态多态。
- 继承 : 砍掉继承,解耦实体之间的关系,组合替代继承,强调组合优于继承。
- 封装 :
- 所有权机制 这个是把双刃剑
- 优势:
- 把C++的核心内存管理思想和遇到的内存问题内化成语法规则,最大程度在编译阶段解决内存问题
- 显示生命周期标注,解决了 C++ 中的悬空引用问题,并且把生命周期分析显式标注,更能直观反应出对象的生命周期
- 劣势: 过于严格的检查,导致代码灵活性下降;并且面向编译器编程
- 优势:
2. Rust 目前最大的问题
代码的灵活性与安全性难以兼得 C++ 很容易写出编译正确但实际运行错误的代码;而 Rust 则很容易写出实际逻辑正确但编译不通过的代码。 借用检查器仍然需要继续迭代和优化。
3. Rust 的三大难点
3.1 内存管理(所有权机制)
很多人觉得所有权机制本身难理解,但我不这么认为。所有权机制的本质,其实是对 C++ 内存管理思想的内化(RAII、手动内存释放等)。 那为什么很多人觉得难? 因为 Rust 在用户层面隐藏了手动内存分配和 RAII 的具体实现细节。初学者无法像在 C++ 中那样,直观地通过代码感受 RAII 的魅力——你可以亲手写一个类的构造/析构函数,看到内存何时申请、何时释放。 Rust 做不到这一点,它把底层思想“藏”了起来,这才导致了所有权机制的理解门槛。
3.2 宏(Macro)
Rust 的宏和 C 的宏完全是两个东西。
- 初学 Rust 泛型时,你会觉得它比 C++ 的泛型少了某些能力。
- 后来才发现,Rust 把很多元编程能力从泛型“移”到了宏,尤其是声明宏。
- 而最难理解的无疑是过程宏。它直接介入编译过程,你需要了解编译原理中的
token、CST、AST等概念,才能写出正确的过程宏。
3.3 Async(异步)
我认为 Rust 最大的失败之一,就是把这套机制叫做“异步编程”。它其实不应该叫这个名字,更应该叫 async 状态机。
- 它本身不具备异步能力,你无法仅依靠标准库轻松写出一个异步程序。它只是可以用来实现异步的底层机制。
- Rust 的
async和 C++ 的协程在设计上是趋同的,但又有很大不同:- 两者本质都是无栈协程,由编译器转换为状态机,都只定义了接口和语义,没有具体的调度实现。
- 但 C++ 的设计语义非常一致,都是围绕协程本身的“挂起 / 恢复”这一语义。
- Rust 的视角则比较刁钻:虽然用了
async/await关键字,但其中的Future、Context、Waker其实是纯粹的 task-runtime 语义。这就导致你要理解async,就完全无法脱离 runtime。而对于初学者来说,自己去实现一个 runtime 是非常困难的。
4. 学习 Rust 需要转变的两个思维
4.1 关注每一个对象的生命周期
你需要学会从对象生命周期的角度去设计程序。 C 和 C++ 程序员已经比较习惯这种思维模式,但和其他语言的开发者交流时,会发现他们往往没有这个习惯。 另外,目前 AI 在复杂项目中,对众多对象的生命周期管理和分析能力仍然欠佳。
4.2 默认移动而非拷贝(针对 C++ 程序员)
C++ 中普通值的默认语义更偏向拷贝;当你想表达资源转移时,通常需要显式调用 std::move。
而 Rust 对非 Copy 类型的默认语义是移动,这一点在思维上需要主动转变。