15

为什么不用Rust?

 3 years ago
source link: http://developer.51cto.com/art/202009/627168.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近我读了一篇批评 Rust 的文章,虽然它提出了一堆好的观点,但我并不认同它 -- 这是一篇容易引起争论的文章。总的来说,我不会推荐一篇批评 Rust 的文章。这是一个遗憾 -- 正视缺点是很重要的,但也需要反对那些草率的或者不准确失误的批判。

所以,下面是我力挺 Rust 的一些理由。

不是所有的开发都是系统编程

Rust 是一种系统编程语言。它提供了对数据布局和代码运行时行为的精确控制,赋予你最大的性能和灵活性。与其他系统编程语言不同的是,它还提供了内存安全--有bug的程序会以一种明确定义的方式终止,而不是出现(潜在的安全威胁)未定义的行为。

然而,在大多数情况下,人们并不需要终极性能或对硬件资源的极致控制。在这种情况下,像 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 中,你需要做出一些选择,有些重要到需要专门的语法。

所有这些复杂性的存在是有原因的 -- 我们不知道如何创建一个更简单的内存安全的低级语言,虽然并不是每个任务都需要用低级语言来解决。

另请参见《为什么C++在瓦萨号沉没时航行》。

https://www.youtube.com/watch?v=ltCgzYcpFUI

编译时间

编译时间是所有工作的倍数。用运行速度较慢但编译速度较快的编程语言编写的代码,可以有机会运行得更快,因为程序员可以有更多的时间去优化代码。

Rust 在通用性的难题中有意挑选了慢速编译器。这并不一定是世界末日(因为由此带来的运行时性能提升是实实在在的),但这确实也意味着在大型项目中,你将不得不为合理的构建时间而拼尽全力。

rustc 实现了可能是生产型编译器中最先进的增量编译算法,但这感觉有点像和语言编译模型打架。

https://rustc-dev-guide.rust-lang.org/queries/incremental-compilation.html

与 C++ 不同的是,Rust 的构建并不是尴尬的并行,并行的数量受限于依赖图中关键路径的长度。如果你有40多个 core 进行编译,这就会体现出来。

Rust 还缺少一个类似于 pimpl 的功能,这意味着改变一个 crate 需要重新编译(而不仅仅是重新链接)其所有的反向依赖。

pimpl 见: https://en.cppreference.com/w/cpp/language/pimpl

成熟度

只有 5 岁,Rust 绝对是一门年轻的语言。尽管它的前景灿烂,但我曾经在“C将在十年后存在”上下的赌注,要比“Rust 将在十年后存在”下的赌注多得多(参见 Lindy Effect)。如果你写的软件要持续几十年,你应该认真考虑选择新技术的相关风险。但请记住,90年代在银行软件上选择 Java 而不是 Cobol,回想起来,证明是无比正确的选择)。

Lindy Effect: https://en.wikipedia.org/wiki/Lindy_effect

Rust 目前仅有一个完整的实现 -- rustc 编译器。另一个最好的替代实现 mrustc,故意省略了许多静态安全检查。rustc 目前只支持一个生产就绪的后端 -- LLVM。因此,它对 CPU 架构的支持比 C 窄,C 架构有 GCC 实现,也有许多厂商特定的专有编译器。

最后,Rust 缺乏官方规范。参考文档是一个正在进行中的工作,还没有记录所有细致的实现细节。

可替代性

在系统编程领域,除了 Rust 之外,还有一些其他语言,主要有 C、C++ 和 Ada。

现代 C++ 提供了提高安全性的工具和准则。甚至有人提议建立类似 Rust 的生命周期机制。与 Rust 不同,使用这些工具并不能保证没有内存安全问题。然而,如果你已经维护了大量的 C++ 代码,那么检查一下遵循最佳实践和使用 sanitizer, 对于解决安全问题是有意义的。这很难,但显然比用另一种语言重写更容易。

如果你使用 C 语言,你可以使用形式化方法来证明没有未定义的行为,否则你只能详尽地测试一切。

Ada 是内存安全的,如果你不使用动态内存(永远不调用 free)。

Rust 是成本/安全曲线上的一个有趣的点,但肯定不是唯一的一个点。

工具

Rust 工具是值得称赞的东西。基线工具、编译器和构建系统(cargo),经常被认为是一流的。

但是,举例来说,一些与运行时相关的工具(最明显的是堆分析)目前还不存在 -- 如果没有运行时工具,就很难对程序运行时进行分析。此外,虽然 IDE 的支持还算不错,但远没有达到 Java 级别的可靠性。如今在 Rust 中,数百万行程序的自动复杂重构还做不到。

集成

不管 Rust 的愿景是什么,今天的系统编程世界还是 C 和 C++ 的天下,这是一个事实。Rust 有意不试图模仿这些语言 —— 它没有使用 C++ 风格的类或 C ABI。

这意味着,它们之间的集成需要明确的桥梁。这些都不是无缝的。它们是不安全的,并不总是零成本的,并且需要在语言之间同步。虽然片断式集成的一般也还能维持,工具也能赶上,但一路上却意外的复杂。

一个具体的小麻烦是,Cargo 独特的世界观(这对纯 Rust 项目来说是个福音)可能会使它更难与更大的构建系统集成。

性能

"使用LLVM" 并不是解决所有性能问题的通用方案。虽然我不知道 C++ 和 Rust 在规模上的性能的基准,但不难列出一个 Rust 不如 C++ 一些性能问题列表。

最大的一个可能是 Rust 的 move 语义是基于 value 的(机器代码级别的 memcpy)。相比之下,C++ 语义使用的是你可以使用数据的特殊引用(机器代码级的指针)。理论上,编译器应该能够看穿复制链,但在实践中往往不能。#57077. 一个相关的问题是没有放置 new -- Rust 有时需要从堆栈中复制字节,而 C++ 可以在原地构造东西。

57077 https://github.com/rust-lang/rust/issues/57077

有点有趣的是,Rust 的默认 ABI(为了使其尽可能高效,它并不稳定)有时比 C 更差。#26494.

https://github.com/rust-lang/rust/issues/26494#issuecomment-619506345

最后,虽然理论上 Rust 代码应该更高效,因为有更丰富的别名信息,但启用别名相关的优化会引发 LLVM bug 和误编译。#54878.

https://github.com/rust-lang/rust/issues/54878

但是,重申一下,这些都是挑出来的例子,有时候这些领域会有相反的情况。例如,std::unique_ptr 的性能问题在 Rust 的 Box 中就不存在。

一个潜在的更大的问题是,Rust 的定义时间检查的泛型,表现力不如 C++。所以,一些高性能的 C++ 模板技巧,在 Rust 中就很难用漂亮的语法来表达。

模板技巧 http://eigen.tuxfamily.org/index.php?title=Expression_templates

Unsafe 的定义

一个比所有权和借用更核心的问题也许是 unsafe 的边界。通过在 unsafe 的块和函数后面划定所有危险的操作,并为它们提供安全的上层接口,就有可能创建一个既是:

合理的(非不安全的代码不能导致未定义行为)。

和模块化 (不同的不安全块可以分别检查)。

很明显,这个承诺在实践中得到了验证:有问题的 Rust 代码会带来 panic,而不是缓冲区超限。

但在理论上来看,问题就不那么乐观了。

首先,没有 Rust 内存模型的定义,所以无法正式检查给定的不安全块是否有效。有非正式的 "rustc 所做的或可能依赖的事情 "的定义,并且在进行中的运行时验证器,但实际的模型是在变化的。所以可能有一些不安全的代码,今天在实践中还可以用,明天可能就被宣布无效,明年又被新的编译器优化打破。

其次,还有一个观察,不安全块其实不是模块化的。足够强大的不安全块实际上可以扩展语言。两种这样的扩展单独使用可能是好的,但如果同时使用会导致未定义的行为,观测等价性和不安全代码。

见: https://smallcultfollowing.com/babysteps/blog/2016/10/02/observational-equivalence-and-unsafe-code/

最后,编译器中存在 bug。

见:https://github.com/rust-lang/rust/issues?q=is%3Aopen+is%3Aissue+label%3A%22I-unsound+%F0%9F%92%A5%22

下面是我刻意忽略的一些东西。

经济学:("雇用 Rust 程序员很难了")--我觉得 "成熟度 "部分抓住了它的本质,它不能还原成鸡和蛋的问题。

依赖性:("stdlib太小/所有东西都有太多的依赖")-- 考虑到 Cargo 和语言的相关部分有多好,我个人不认为这是一个问题。

动态链接:("Rust 应该有稳定的ABI")--我不认为这是一个强有力的论点。单态化与动态链接在根本上是很不兼容的,如果真的有需要,还有 C ABI 可用。我确实也认为这里有可改善空间,但我不认为这种改善需要针对 Rust。见:https://internals.rust-lang.org/t/a-stable-modular-abi-for-rust/12347/10?u=matklad

英文原文:

https://matklad.github.io/2020/09/20/why-not-rust.html


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK