《我看过的 Rust 的历史》
Rust 语言最近相当火热,被很多人认为是会在未来取代 C/C++ 的语言。好像是个公司就得用 Rust 做“后端”,没有其它选择了一样。这不得不让我想起 Rust 最早时候的历史,以及最近我发现的 Rust 语言的严重设计问题。
2009 年我还在 IU 的时候,有两个半路出家的同学,本科都不是计算机专业的,在 Dan Friedman 的课上完全迷糊,痛苦不堪那种。每每在课上讲自己对于 Friedman 给的“脑筋转弯题”的结果,我也在场的时候,他们声音都是发颤的,因为他们一本正经好像自己很懂,却又怕被我问出破绽来。他们也声称做出过 CPSer,最后给出的结果却是错的。仔细观察,我发现他们根本不理解里面的原理,只是做了个“样子”,像有那么回事而已。
然而这样的人确实很会拍胸脯,会把握关键词,时机和人际关系。所以后来暑假结束时,我忽然收到通知,他们要给大家做一个关于“Rust 语言”的讲座。原来那个暑假,他们两个去了 Mozilla 实习,项目就是开发 Rust 语言。那就是我第一次听说 Rust 语言。
然而他们做的这个 Rust 的演讲却空洞无物,全是大口号。他们的幻灯片上画了一个大三角形,提出 Rust 的”三大特色“,当然其中包括了”安全“(security),另外两个我不记得了。当时,他们号称 Rust 使用静态分析,实现了完全没有 GC(垃圾回收)的内存管理。整个讲座就像是商业宣传,没有任何实质的技术内容,全是口号。
看了这个讲座,我根本不觉得 Rust 这语言能发展下去。我觉得就是 Mozilla 瞎扯淡一阵子而已,说要用这新的语言开发一个浏览器内核,结果一直也没做出来。Mozilla 有很多类似的“研究项目”,最后都不了了之。之前一篇文章提到过的 DrJS 静态分析也是其中一个例子https://yinwang.org/posts/cfa。
Rust 的设计者是谁,他有什么功底?他对语言理解有多少?我根本看不出来。如果他真的透彻理解了编程语言,他应该不会选择 OCaml 那样的语言来实现 Rust 的第一个版本。
很多年后,虽然浏览器仍然是 C++ 写的,却没想到 Rust 居然在其它地方热门起来了。
后来这其中一个 Rust “先驱”同学开始做一个针对 GPU 的语言,号称要让 GPU 可以实现复杂的,类似树或者图那样的数据结构,实现通用的计算。我一看就不像是可以成功的项目,因为 GPU 设计来就是做非常简单的事情的,不能用来实现复杂一点具有依赖关系的数据结构,否则就不能并行计算了。当然最后那项目根本不能用,但他还是靠那东西拿到了 PhD。后来 Rust 居然热门起来,所以他就在某大公司继续从事 Rust 编译器的开发。
“完全没有 GC,静态的内存管理”,这其实是我那时候最感兴趣的话题。我也不满意 Java 等语言的 GC 带来的随机停顿,所以我花了挺多时间来琢磨内存管理应该如何设计,这样可以完全依靠静态分析,不需要任何动态的垃圾回收。这个理想,就像 Rust 当时所号称的那样。
我琢磨了一段时间,实验了各种内存模型和静态分析。然而最后,这一切的梦想都被 Kent Dybvig 的一句话打破了。我还记得,我在 Kent 的办公室里说:“我想实现这么一个完全依靠静态分析,不用 GC 管理内存的语言。” 结果 Kent 冷静地说:“完全静态的内存管理,这可能吗?内存管理本来就是一个动态过程。“
听了这话,我才开始反省之前的理想。我重新分析了编程语言的内存模型,发现它确实是一个不可计算的动态过程,等价于“停机问题”。这么简单的道理,我为什么没想到呢?
我又提出,也许应该使用自动的引用计数进行内存回收,这样就不会有 GC 的随机停顿了。Kent 又告诉我,引用计数是有比较大开销的,很多时候性能还不如 GC,使用 GC 根本就不是问题,主要看你怎么实现它。
看来我思考过的一切,Kent 都已经思考或者经历过了,毕竟他开发 Chez Scheme 的编译器和 runtime 已经超过 30 年之久。我不得不佩服他的 Chez Scheme 编译器,它不但具有闪电般的编译速度,而且有合理的 GC 设计。它的垃圾回收器不但效率高,而且是可以让用户自己配置的。如果 GC 出现长时间停顿,用户可以通过调整参数改变它的行为。
Chez Scheme 为什么编译速度这么快?Kent 的回答是:因为它不做那些针对“愚蠢代码”的耗时的优化,它假设程序员有基本的素养,只做稍微聪明一点的,不很费时间的优化,而且它选择了正确的数据结构。总的说来,Chez Scheme 只能说是“不傻”而已。
我猜,他没说出来的话是,其它的编译器都比较傻。
所以 Chez Scheme 这一切优势,都来自于设计的智慧,而不只是“聪明”和蛮干。它并没有实现特别高级的“技术”,它的优势来源于选择了正确的方向。
然而看看现在的 Rust 呢?很显然,当年的“完全静态分析”实现内存管理的理想已经破灭,Rust 也需要使用引用计数(Rc,Arc)。就像 Kent 说的,完全静态的内存管理是不可能的。
而且 Rust 还有“unsafe 代码”,因为已有的静态分析造成的限制,让它无法写出所有需要的代码,所以很多时候不得不跳出安全机制,使用“unsafe”关键字,使用没有静态安全保证的代码。最新的 PL 研究,有一些就是针对 Rust 这些 unsafe 区域的,什么 Stack Borrow,Tree Borrow 等概念。然而它们做的其实是加入一些限制,结果你又没法写出应该可以表达的代码了。这很像 Haskell 社区,给 Hindley-Milner 类型系统做出各种“宽容”的改动,稍微放宽表达能力,但仍然无法逃脱它的局限性,反而搞得更难理解。
然而即使一再地后退,又有引用计数,又有 unsafe,Rust 社区却仍然坚持使用“完全静态保证”这样的广告词。如果你指出这些例外情况,他们就改口说:这是 Rust 的哲学。
Rust 社区的“静态保证”口号传播如此广泛,如果你有异议,就连刚入门的编程新手都会来“教育”你。甚至不需要人来教育你,如果你跟 ChatGPT 说这事,它都会跟你说:这就是 Rust 的哲学!你觉得不对那是因为你不懂它的哲学,所以你觉得理解很困难。
“哲学”,好一个高大的词汇。也就是说,这是一个“理想”或者希望做到的事情,一直没有做到,而且把事情越搞越复杂。但这是一种哲学,所以你得崇拜和顺从我们!
这就是为什么我虽然完全理解 Rust 的所谓“借用”,“生命周期”等概念,现在又有了 AI 工具可以帮我折腾,却一直不用 Rust。因为我不需要极致的性能,而且就算我需要,我也不相信 Rust 真的可以取代 C/C++。
在“计算机科学基础班第5期”透彻地教会了大家 Rust 的内存管理机制,甚至用 Rust 写出了函数式语言的解释器这么复杂的代码之后,我却更加欣赏 C 和 C++ 了。如果真有需要极致性能的场合,我会选择 C/C++,而不是 Rust。