【外评】Why Not Rust?
我最近读了一篇批评 Rust 的文章,虽然它提出了一堆好观点,但我并不喜欢–这是一篇容易引起争论的文章。总的来说,我觉得不能推荐批评 Rust 的文章。这是一个遗憾–正视缺点是很重要的,而揭穿低劣的/错误的批判尝试则会对真正好的论点产生不利影响。
因此,我在此尝试反驳 Rust:
并非所有编程都是系统编程
Rust 是一种系统编程语言。它能精确控制数据布局和代码运行时的行为,为你提供最高的性能和灵活性。与其他系统编程语言不同的是,它还提供了内存安全性–有漏洞的程序会以定义明确的方式终止,而不是释放(可能对安全敏感的)未定义行为。
然而,在许多(大多数)情况下,人们并不需要极致的性能或对硬件资源的控制。在这些情况下,Kotlin 或 Go 等现代托管语言可以提供不错的速度和令人羡慕的性能时间,而且由于使用垃圾回收器进行动态内存管理,因此内存是安全的。
复杂性
程序员的时间是宝贵的,如果你选择了 Rust,就需要花费一些时间来学习。Rust 社区投入了大量时间编写高质量的教学材料,但 Rust 语言规模庞大。即使 Rust 实现能为你带来价值,你可能也没有资源投入到增长语言专业知识上。
Rust 为提高控制能力所付出的代价就是选择的诅咒:
struct Foo { bar: Bar } struct Foo<'a> { bar: &'a Bar } struct Foo<'a> { bar: &'a mut Bar } struct Foo { bar: Box } struct Foo { bar: Rc } struct Foo { bar: Arc }
在 Kotlin 中,您只需编写 class Foo(val bar: Bar)
,然后继续解决您的业务问题。在 Rust 中,你需要做出一些选择,有些选择甚至重要到需要专门的语法。
所有这些复杂性都是有原因的–我们不知道如何创建一种更简单、内存安全的底层语言。但并非每项任务都需要低级语言来解决。
编译时间
编译时间是一切的乘数。用运行速度较慢但编译速度较快的编程语言编写的程序,运行速度会更快,因为程序员有更多时间进行优化!
Rust 在泛型困境中有意挑选了慢速编译器。这并不一定是世界末日(由此带来的运行时性能提升是真实的),但这确实意味着在大型项目中,你将不得不为合理的构建时间拼尽全力。
rustc 实现了可能是生产编译器中最先进的增量编译算法,但这感觉有点像与语言编译模型作斗争。
与 C++ 不同,Rust 的编译并不存在令人尴尬的并行性;并行性的大小受限于依赖关系图中关键路径的长度。如果你有 40 多个内核需要编译,这一点就会显现出来。
Rust 还缺乏 pimpl 成语的类比,这意味着更改 crate 需要重新编译(而不仅仅是重新链接)其所有反向依赖关系。
成熟度
Rust 诞生五年,绝对是一门年轻的语言。尽管它的未来看起来很光明,但我赌 “C 语言十年后还会存在 “的钱比赌 “Rust 十年后还会存在 “的钱要多(见林迪效应)。如果您要编写的软件能使用几十年,您就应该认真考虑选择新技术的风险。(但请记住,90 年代在银行软件中选择 Java 而不是 Cobol,事后证明是正确的选择)。
Rust 只有一个完整的实现–rustc 编译器。最先进的替代实现 mrustc 故意省略了许多静态安全检查。因此,它对 CPU 体系结构的支持范围比 C 语言要窄,C 语言有 GCC 实现和许多供应商专用的专有编译器。
最后,Rust 缺乏官方规范。参考文献正在编写中,还没有记录所有精细的实现细节。
替代语言
在系统编程领域,除了 Rust 之外还有其他语言,主要有 C、C++ 和 Ada。
现代 C++ 提供了提高安全性的工具和指南。甚至有人提出了类似 Rust 的生命周期机制!与 Rust 不同的是,使用这些工具并不能保证不存在内存安全问题。现代 C++ 更安全,Rust 更安全。不过,如果你已经维护了大量的 C++ 代码,那么检查一下遵循最佳实践和使用 sanitizers 是否有助于解决安全问题是很有意义的。这很难,但显然比用另一种语言重写要容易得多!
如果使用 C 语言,您可以使用形式化方法来证明不存在未定义的行为,或者直接对所有内容进行详尽测试。
如果不使用动态内存(永远不要调用 free),Ada 就是内存安全的。
Rust 是成本/安全曲线上一个有趣的点,但远非唯一的点!
工具
Rust 的工具有好有坏。基线工具、编译器和构建系统(cargo)经常被认为是一流的。
但举例来说,一些与运行时相关的工具(最明显的是堆剖析)并不存在–如果没有运行时,就很难对程序的运行时进行反思!此外,虽然集成开发环境的支持还算不错,但其可靠性远不及 Java。如今,Rust 无法对数百万行的程序进行复杂的自动重构。
集成
不管 Rust 的承诺是什么,当今的系统编程世界说的是 C 语言,居住的是 C 和 C++,这是不争的事实。Rust 并没有刻意模仿这些语言–它没有使用 C++ 风格的类或 C ABI。
这意味着两个世界之间的整合需要明确的桥梁。这些桥接并非天衣无缝。它们是不安全的,并不总是完全零成本的,而且需要在两种语言之间同步。虽然片段集成的总体承诺得以实现,而且工具也跟上了步伐,但在实现过程中还是会出现意外的复杂性。
一个具体的问题是,Cargo 固执己见的世界观(这对纯 Rust 项目来说是个福音)可能会让它更难与更大的构建系统集成。
性能
“使用 LLVM “并不是解决所有性能问题的通用方案。虽然我不知道 C++ 和 Rust 的大规模性能比较基准,但不难列出一系列 Rust 相对于 C++ 性能有所下降的情况。
其中最大的可能就是 Rust 的移动语义是基于值的(机器码级别的 memcpy)。相比之下,C++ 的语义使用的是可以窃取数据的特殊引用(机器代码级的指针)。理论上,编译器应该能看穿复制链,但实际上往往不能: #57077. 与此相关的一个问题是没有放置新数据–Rust 有时需要将字节复制到堆栈或从堆栈中复制,而 C++ 则可以就地构造。
有点可笑的是,Rust 的默认 ABI(为了尽可能提高效率,该 ABI 并不稳定)有时比 C 语言还糟糕: #26494.
最后,虽然理论上 Rust 代码应该由于明显更丰富的别名信息而更高效,但启用别名相关的优化会引发 LLVM bug 和误编译: #54878.
不过,我还是要重申,这些都是挑选出来的例子,有时情况并非如此。例如,std::unique_ptr 存在性能问题,而 Rust 的 Box 则缺乏。
一个潜在的更大问题是,Rust 通过定义时间检查泛型,表现力不如 C++。因此,一些 C++ 模板的高性能技巧在 Rust 中无法用漂亮的语法表达出来
不安全的含义
对于 Rust 来说,比所有权与借用(ownership & borrowing)更为核心的思想或许就是不安全边界(unsafe boundary)。通过在不安全代码块和函数后面划定所有危险操作,并坚持为它们提供安全的高层接口,可以创建一个既
- 健全(非不安全代码不会导致未定义的行为)、
- 和模块化(可分别检查不同的不安全代码块)的系统。
很明显,这一承诺在实践中是可行的:对 Rust 代码进行模糊测试会发现恐慌,而不是缓冲区超限。
但理论前景并不乐观。
首先,Rust 没有定义内存模型,因此无法正式检查给定的不安全块是否有效。虽然有 “rustc 所做或可能依赖的事情 “的非正式定义,也有正在开发的运行时验证器,但实际模型还在不断变化。因此,可能有一些不安全代码今天还能正常运行,明天就会被宣布无效,明年又会被新的编译器优化所破坏。
其次,我们还注意到,不安全代码块实际上并不是模块化的。功能足够强大的不安全代码块实际上可以扩展语言。两个这样的扩展单独使用可能没有问题,但同时使用就会导致未定义的行为: 观察等价和不安全代码。
最后,编译器中还存在明显的错误。
以下是我特意省略的一些内容:
- 经济学(”招聘 Rust 程序员更难了”)–我觉得 “成熟度 “部分抓住了问题的本质,而这并不能归结为鸡生蛋还是蛋生鸡的问题。
- 依赖性(”stdlib 太小/所有东西都有太多的依赖性”)–鉴于 Cargo 和语言的相关部分有多好,我个人认为这不是问题。
- 动态链接(”Rust 应该有稳定的 ABI”)–我不认为这是一个有力的论据。单态化从根本上就与动态链接不兼容,如果你真的需要,还有 C ABI。我确实认为这种情况可以得到改善,但我不认为这种改善需要针对 Rust。
本文文字及图片出自 Why Not Rust?
你也许感兴趣的:
- Android 全力押注 Rust,Linux 却在原地踏步?谷歌:用 Rust 重写固件太简单了!
- C 语言老将从中作梗,Rust for Linux 项目内讧升级!核心维护者愤然离职:不受尊重、热情被消耗光
- 从电梯故障到编程新宠,Rust为何连续七年称霸「最受推崇语言」
- 【外评】不要把 Rust 写成 Java
- 语言设计: Rust 的几乎规则
- 美国国防部建议将C代码转换为Rust
- 【外评】 我使用(并喜爱)Rust 已经有 10 年了, 以下是它让我失望的地方
- 【外评】为什么我希望不要让 Rust 锈化一切?
- 【外评】Rust 版的 Linux 文件系统
- Vue诞生10年,创始人尤雨溪推动“锈化”——通过Rust提升Web基础设施性能
你对本文的反应是: