Rust是否过度限制了程序员的能力发挥呢?
首发于知乎:https://www.zhihu.com/question/364980266/answer/1687038599
啊?

Rust 的哲学不是「严格限制」,而是 Be explicit。这两者还是有一些细微差别的。
Rust 那么大的 unsafe 和那么大的 memory leak 后门;std::mem 那么多初级黑魔法的宝库,都放在同一个箱子里,不比 C++ 东一块西一块的魔法用起来爽吗?语法限制的话,Rust 的语法容忍度比 C++ 好多了吧,可以多逗号,可以没/多分号,万物皆表达式(结合块语句,意味着我们可以在几乎任何地方塞一个代码段),默认移动等等,都是成吨的糖啊。说 Rust 的语法严格,不如说 Rust 的语法与 C 系语法有很大差别,所以 C 系程序员不太适应罢。学 ML 的时候可没有批评人家表达力弱的,怎么在 Rust 这里就不怪自己怪语言了呢?(尝试暴论以吸引注意)
咳咳——

Rust 的哲学完全不会限制程序员能力的发挥。
所谓「能力的发挥」,归根结底还是程序员思想的表达。这种思想,或者说逻辑,指的是程序员希望程序的每一步如何表现。一种语言,越能方便程序员把思想表达出来,就越有利于程序员能力的发挥。
编程中的思维表达,要求每一步都不能失真,必须准确而细致。程序员下的每一个命令,他都需要准确理解这个命令的行为表现,才不会使得程序的表现出现偏差。这个过程本身就是困难的,不然我们就不会戏称自己「写 Bug 的」。
一些语言给人「容易」的错觉,主要是因为它们使用一些机制,接管或推迟了这种思想的表达与校正。前者的一个例子是,原生 GC 语言可以接管内存管理的任务;后者的则是动态语言的调度火葬场、C++ 的 Data Race 火葬场。
插一句,GC 也很难解决资源管理的全部问题,所以 GC 语言必然得带上一个蹩脚的 RAII,比如 Java 的 try with resource。
抽象层级越高,控制力度就越弱,这是不言而喻的公理。如果要用 RAII 操作内存,就必须接受它带来的固有的复杂度。弄明白眼花缭乱的「约束」到底是 overhead 还是固有的,这一点很重要。
有时候,我们写代码并不能记全自己需要表达什么。我们会忘记一些重要的东西,而如果语言能发现并提醒我们,而不是靠逆向手段与黑盒手段,那肯定是极好的。
对比 Rust 与 C++,我认为 Rust 做得更好;因为 C++ 那面我们还得学 Destruct 的次序(全局变量 construct / destruct 试试?),学三五法则,学 virtual destruction,学 Ctor Exception Safe。这些在 C++ 那儿不是以语法形式约束的,而是以 Cpp Core Guideline 的形式。问题在于,不会这些东西,写 C++ 程序 100% 必炸,而且炸得连妈都不认识。所以把它们做成硬性的约束,难道不是很必要的吗?
经常被吐槽的一点是 Rust 编译器的「话唠」(Rust 编译器已经很克制了吧,看看人家 clippy 的 pedantic 模式?)。但说句公道话,Rust 的限制,不全是像 Java 那样「你只能用树形继承这一种方式写我的代码」的约束,更多还有询问性质的约束。后者这些,不是我们必须发明学习各种模式与 Guideline 才能玩转的东西(Java 的 XML 配置跟反射搞得快吐了),而是「啊,这是我忘记考虑的东西,你的提醒真是帮大忙了」这种惊叹。
当 Rust 提醒「生命周期」问题的时候,我们的确会发现,是自己对变量的生存时长认识模糊了。
当 Rust 提醒 Sync + Send 问题的时候,我们的确会发现,是多线程有潜在竞争的问题。
当 Rust 提醒我们有 Result 没加 ? 的时候,我们的确会发现,是对异常情况处理有纰漏。
我一下子能想起来的不太多。——这些各种,有时候推理一波,我们还能发现程序逻辑上的问题,甚至发现架构设计上的不合理之处,比如一个 Storage 对象它不应该拥有一个 Session。
有时候看到顾左右而言他的言论,说什么加上完备的测试与良好的 linter,也可与 Rust 相匹敌。那么,古尔丹,代价是什么呢?反向纠错的成功率更低,这种明显的话尚且不论;更长的纠错周期,更多的人力投入,更复杂的环境配置,都是不应该被忽略的点。以前各种设施散落成独立的组件,我们不得不用大量的经验才能把它们拼起来。没有任何一本 C++ 教材会教我们「你必须学 CMake, Cpp Core Guidelines, Google Test, Boost 等其它东西才有资格在 C++ 的世界里踩 Bug」,而 Rust 把它们打包在一起:「我把它们都整合好了,你把这一套都学完,就可以去摸爬滚打了。」
把 Rust 一整套解决方案跟其它单独语言对比,这不是欺负人玩儿呢么?

虽然 Rust 语言的哲学并无大碍,但是,Rust 目前的问题仍然很多。我列几个比较明显的:
-
Rust 对象的生命周期仍然存在设计问题。以前经常出现 not live long enough,需要加一个变量绕开;现在虽然改了一些,但是链式调用里经常可能出现这个问题。
-
Rust 的借用命名误导很大,
&与&mut在单线程语境下理解为「不可变引用」与「可变引用」问题不大,但它们本质的意义是「静态共享引用」与「静态独占引用」,相信用 Rust 写多线程的朋友们一定被所谓 interior mutability 唬过一阵子。 -
Rust 没有完整的 Downcast,尽管有个
Box::downcast。所以写 OOP 的时候,需要自己加额外的 struct 绕开。当然尽可以说 downcast 不符合 Liskov 原则,但总有这种场合:「我把一个对象传给一个接口,然后它回来了,却变成了 type erased 的形状,我还没法把它变回去?」这个问题在传 lambda 的时候尤其明显。 -
Rust 的 pattern match 残废;一个 match 的 arm 即使加上 if 也表达力不够,比如下面这个:
Some(x) | None if ...if 对
|后面的生效还是对整个生效?It's a question。结果不得不分开写重复的语句。 -
(这个可能更算是语法糖范畴)Rust 没有 Partially applied function,没法写出下面这种代码:
iter .map(add(3, _))这个问题,以及其它一些小问题,比如没有尾递归优化,使得 Rust「看上去像函数式,但其实根本写不了舒服的函数式」。
-
(已经修补了)Rust 没有返回
impl trait对象的方法,所以标准库里可以看到一吨奇怪的Iterator作返回值。当我们想自己写一个返回某 Iterator 的函数的时候……

国内高校应该教 Rust 而不是 C,然后在考卷上要求学生写出「一个迭代器链式调用的返回值的正确类型」,这不比
++i+++++i++爽吗?(滑稽 x2)
就算这样,Rust 本身仍然是一个迭代式进化的、工程水平很高的项目。从 2015 年到现在,可以看到开发组的大佬们分得清主次,明白应该把精力放到什么地方。虽然早期有各种各样的小毛病,但后面都可以补得好。毕竟任何工程都无法一下子做到完美,指望 Rust 在娘胎里就设计定型,这难道不是瀑布式思维的经典误区吗?


浙公网安备 33010602011771号