批判Rust语言,以及C/C++为什么永远不会死
此篇文章转载自 Scott Huang 的 GitHub,以便更多语言爱好者学习和交流,尤其是C/C++和 Rust,希望对各位有用。
以下为原文翻译:
简单讲,原文 http://eax.me/cpp-will-never-die/是俄语,有人感兴趣,得到作者同意后,把它翻成英文。(译者:然后我再把它翻成中文。)
显而易见,这篇博文将会导致一场语言大圣战,所以,请思考两遍,确定你将会通过“有建设性的辩论”的评论参与讨论后再开始阅读这篇文章。
再次说明原文是俄语:)
注意:进一步讲,我冒昧的认为 Rust 有意尝试创建一个快速并且安全的语言。毕竟,Mozilla 的人最初构思用它作为工具来开发一个浏览器引擎。如果它被证明是另外一个仅仅安全的语言,那么我认为它没有达成目标。那里有许多非常不同的安全语言供人们 选择和品味,如果 Rust 没有打算代替C++,那么:
- 为什么它需要包含一个不安全子集;
- 并且,为什么作者要抛弃 Rust 的轻量级进程?毕竟它们很方便,对吧?换句话说,如果我假设错了,那么整件事情就没有讨论的意义了。
如有你碰巧偶尔逛逛 linux.org.ru 论坛,那么请被提醒到这篇文章没有触及为什么不喜欢 Rust 的那 10 条纯技术理由。一条和亲爱的伙伴@sum3rman 的 Skype 交谈透露出不止一种的“技术性”理由的看法。所以,我不得不承认,我下面罗列的东西不讨人喜欢,但是我还是冒险从中引用一些最感兴趣的条款到这里。实际 上,一些普通共识的理由自己就足够大到不用触及技术性的讨论。
对于每一个理智程序员都非常清楚的知道的C/C++在近期不会死掉。没有人会尝试重用新语言新编写几乎所有已经存在的桌面应用程序,操作系统内 核、编译器、游戏以及浏览器引擎、虚拟机、数据库、压缩工具、音视频编码解码器、一堆其他的C库等等。这是一批数量巨大的快速的、调试过的被时间证明了的 代码。重写的代价太昂贵了和太冒险了,并且,诚实的讲,除了一些疯狂的 Rust 粉丝,没人会认为这有意义。对C/C++程序员的需求从来都是高的,并且在未来很长一段时间都是。
那么用 Rust 写一些新的代码怎么样?
嗯,你也许记得,这并不是第一次尝试创建一个“更好的”C/C++。拿D语言来举例。它在 2001 年发布,且确实是一个好的语言。但没有空间发展,没有合适的开发工具,没有著名的成功案例可以联想到它。OpenMW 项目最初用D开发,但作者突然决定用 C++ 从头重写。据他们坦白,他们收到一大堆的邮件说,“你们创建了一个很酷的项目,我想贡献一些力量给它,但是我们不懂,也不喜欢学习这个愚蠢的D语言”。维 基百科告诉我们,除了D,还有有其它尝试准备杀死C++ - 举例说 Vala、Cyclone、Limbo、Bitc。有多少人曾经听说过这些语言?
我觉得人们必须理解从历史中得到教训。没有一个理智的人会在他们的项目中开始使用一个新语言,直到你展示一些非常酷的开发支持工具,告诉他们一 些成功故事,并且证明一堆程序员靠这个语言做日常工作维持生活。作为程序员,他们从来不会 - 除了一些最年轻的人 - 花他们的时间和健康来学习另外一种“非常棒”的语言,直到你展示一些非常酷的开发工具(不是未完工的像 Racer 工具那样)和许多确实准备好的库(不是“实验性的”或者“不稳定的”东西),告诉他们一些成功案例,告诉他们有许多空缺在他们的城市或乡镇。你知道,这就 像“鸡和蛋”的两难处境。只有非常少的机会,这个问题确实得到解决(最近相关的案例是 Go 和 Scala) - 这得感谢一些大公司(Google、Typesafe)的时间和金钱投入,他们基于某种理由认为值得推广一个新语言。
正如我提到的,有许多非技术性的理由单独就可以质疑 Rust。但是,让我们假想一会儿这些理由都不存在。那么没有理由不用 Rust 写程序,对吧?好的,这一点仍然非常可疑,这么说吧。
C/C++被从很多方面批判。顺便说一下,大多数批评者还从来没有看过产品级的 C++ 代码。简短的说,C++的问题是非常快(并且只需求一点点内存、电量,等等),但是从允许数组越界,自由的存取内存等方面看不够安全。过去,这个问题促使 程序员们开发出一系列安全的语言,比如 Java、C#、Python 还有其他等等。但是,他们被证明和 C++ 相比,对资源需求太多,同时还有其他一些不足 - 比如,比如当进行垃圾回收时“世界停止了”的问题。这也是为什么程序员争扎地去创建一个和 C++ 一样快,但安全的语言。Rust 是其中一个候选人。
Rust 确实是安全的,但是,不幸的是,离快还差很远。在写这篇文章的时候,它和 Java,go 和 Haskell 的性能如下图所示:
我真诚的希望程序员找到一个方法来及时的加速,但直到那时,几乎没有语言比 Scala 或者 Go 对安全/速度进行妥协更加感兴趣。是否可以使一种语言同时具有速度和安全,或者是否由于对数组越界,安全包裹C语言库,或则其他一些类似东西而天生注定比 C/C++慢两倍的问题仍然悬而未决。
顺便问一下,什么实际上使得 Rust 安全?简单的说,这门语言有内建的代码分析器,非常艰难的一条:它可以捕获全部典型的 C++ 错误,不仅处理内存管理,而且同时考虑多线程。通过一条管道来传递一个指定对象的引用到另外一个线程,并且接着由你自己尝试使用这个引用 - 程序会拒绝通过编译。这确实非常酷。
但 C++ 在过去 30 年一直屹立不倒,有大量的静态的和动态的分析器在这些时间段被发布出来。举一个例子,看一个关于 Google sanitizers(明智分析器)的一个短片 - 他们确实非常难。不管怎么说,在任何一个严肃的项目中,你使用一个不断集成的系统,运行一大堆的测试来编译程序。如果你不这么做的话,那么你的麻烦比语言 缺乏安全性而言会更糟糕,因为静态的类型不会保证程序按你的逻辑正确的运行!所以,由于你总是运行测试,为什么不同时使用。另一方面,如果你在你的代码的 某个深处地方没有检查数组越界,并且 Sanitizer 也没有报告这个错误,也许,这仅仅由于所有必须的检查已经在上一层提供过了,同时,多一次检查不是会使程序变慢?即使没有 sanitizers,你还可以发现许多东西供你在不同平台编译项目时提供伴随适当的失真的"assert (obj -> isvalid)"风格论断来检查你代码的不变性。粗糙的说,这个问题实际上源自过去那些好的圣战,关于异教徒和加尔各答接近软件开发(指的是,一项创新 太理想化的尝试和一个传统经验主义者认为被前者的支持者无心简单化了 - 原译者注)。
你经常可以听到一个争论说,90% 的执行时间被 10% 的代码所执行(据我理解,仅仅是一条经验主义 - 对一个主题快速的扫描互联网无法替代任何严格的科学的研究)。因此,你可以用安全的 Rust 语言写你大部分的代码,然后,剩下的 10%(那些“热”代码)写在不安全的子集中,所以现有的 Rust 实现实际上不会有不好的性能。好的,但这不更是暗示我根本不需要 Rust,因为我可以用 Go 写 90% 代码,然后用C写剩下的 10%?只有那些寻找银弹的人和幻想神话的异教徒会因为所有代码都 100% 用同一种语言编写而感到开心,而用 Rust。但实际上一种语言有两者方言,这和"Java + C"或者"Go+C"组合没什么不同。
但实际上 90/10 规则是垃圾话。按照它的逻辑,我们可以用 Java90% 重写 Webkit 或者 VirtualBox 或者 GCC 而得到同样的结果。但这显然是错的。并不是这个比率因不同程序而变动太大,让我们做一些计算来瞧瞧。假设整个程序用不安全的C/C++编写,并且它的执行 时间,假设是 0.91(一小部分热代码)+0.11(大量的冷代码)=1。现在和一个用一个带有C代码插入的安全语言编写的程序做比较: 0.91 + 0.12 = 1.1,这样,理论上说,区别是 10%。是多了还是少了?这取决于项目的规模。以 Google 为例,即使是很少的一点比例都可以节省大量的金钱(请看第五小结,“利用”,在这篇文章中)。或者想象当下一次更新,JVM 突然开始要求多 10% 的资源!我都害怕猜想需要多少个 0 在数字后面才能把比率转化为金钱。10% 是C和 C++ 完成任务所需要的所有份额。
我们不停的念咒语“不成熟的优化是所有邪恶的根源”。但如果我们想逐字的跟随,为什么在所有代码里不用冒泡排序来替换快速排序?毕竟,我们没有 办法确切的知道哪里是瓶颈,对吗?为什么要把普通的活动包含在 actors 或者事务内存中而不用马上用更加有效率的原子?并且,通常说,在小案例中,强制性的初始化每一个单独的变量,执行一堆辅助性的检查等等根本没有意义。让你 仅花额外的几分钟去考虑而获取即使只有2~5% 而不是 10% 的性能改进,其实也不坏。此外,就像我们已经指出的,这会使C/C++程序有巨大的不同!毕竟,谁敢争辩说找到了热点,重写那些代码(也许有一堆)并且证 明这真的变快了是一项比预先考虑性能而言更轻松的工作?
即使除了速度/安全问题比较之外,我还怀疑那语言的设计。特别对于它使用 5 种指针类型。一方面,让程序员仔细考虑他们的变量存放在栈或堆里,允不允许被多线程操作的想法并不差。另一方面,想象你在写一个程序,且在某一刻发现一些 变量应该存在堆里而不是栈上。所以你用 Box 重写代码。接着你发现你实际上需要 Rc 或者 Arc。另外,你重写了所有的代码。接着,再次,你再次重写它在栈上拥有普通变量。所有的东西你都手工操作而没有使用一个合适的 IDE。正则表达式没有帮助。或者你刚刚结束一个噩梦像“Vec>>>” - 对 Java 说 hello 吧!但悲伤的事情是编译器已经知道每一个变量的每一件关于使用期的事,并会自动插入所有这些 Box's、Arc's等等。但由于某种原因,这个责任被转移给了程序员。让程序员简单的写 val(我们活在第三个千年,毕竟!)会更加方便。并且显式的在需要的地方指定特殊的 Box 或者 Rc。从这一点看,Rust 的开发人员搞砸了整件事情。这个,特别的,让 Rust's的使用范围更加窄了。没有一个理智的人会用这样的一个语言写 web 或者服务器端软件 - 特别当考虑到它并没有提供比 JVM 相关语言更显著的优势。即使是 Go - 带有普通轻量级的进程(不是未来) - 看起来都是一种更好的选择来解决这些任务。至于未来,你得学会如何正确的操作它们而不会砸到自己的脚 - 并且你谈到“安全”的语言,啊?确实,所有的这些语言都有他们自己的独特的怪癖 - 举“整个世界都停止了”的例子。但这个问题可以通过把代码分解成小的服务或者通过其它技术而解决。并且是的,没有人愿意把 Rust 转为 Javascript,通过它来为 AWS 写脚本或则为 MongoDB 来做查询语言。至于安卓,它也不容易可信,但是有一个不同的理由:不仅仅只有一个架构风格,所以 JVM 可以做的更好。所以,如果你恰巧认为 Rust 是“适合所有任务”的话,我让你失望了。
还有更多的理由来终结它:
宏使用一个拐杖来弥补由于缺乏普通异常处理而导致的过度冗长。我已经写了关于元编程的问题 - 就是因为他们是特别的,导致我们没有办法得到一个合适的 Rust IDE。并且,即使我并不确定,看起来 Rust 宏甚至并没有命名空间。Cargo 积极的鼓励绕过 Crates.io 从 git 中直接下载各种仓库就当人们是白痴。作为一种结果,我们有被被巨量的混乱的包终结的风险,就像 Erlang 世界中的 Rabar。顺便,我怀疑 Go 世界有同样的麻烦。就像许多新语言,Rust 在走简单化的路。我通常理解为什么它没有合适的继承和例外,但事实本身是有些人替我做决定让我觉得有些不舒服。C++不会限制程序员说哪些他们可以或者不 可以做。现在,由于我们在走简单化的路,为什么不抛弃所有那些语言扩展?目前组成 Haskell 世界的那些东西是每个程序员用他们自己方言码出来的。智能指针,让你知道,远不会没有代价,也并不会确保一个固定时间的垃圾收集。假如一些线程荣幸的释放 一个非常深的数据结构会发生什么?当死引用在一个迷宫里流浪时,所有依赖它的其他线程都安静的耐心的等待着。Erlang 以及它的一小片有同样的困难 - 我曾经自己面对过它许多次。智能指针自己本身也有一些问题 - 例如内存碎片化和泄漏。就像让一个弱指针在一个循环结构里 - 整件事情搞砸了。所有这些都是一个语言试图假装变得安全点...如果你需要一个固定的 GC 时间,学习你程序的行为,减轻负载并且采取预防性(举例,提供对象池)如果你不满意这些数字,或者可以手工管理内存。有人看到 Rust 里面严格的语义描述吗?它至少有一个内存模型吗?当你考虑到它可以用 10 种方法翻译源代码,你还会叫它为一个“安全”的语言可以写出“确保正确”的程序?我不能,但再一次提醒你麻烦的根源通常是人,而不是技术。如果你的 C++ 代码没有足够好,或者 Java 代码很痛苦的运行缓慢,这不是由于这个技术不好 - 这是因为你还没有学会如何正确的使用它。因此,你也不会因为其他一些理由对 Rust 满意。学习那些更流行的工具并且喜欢上它不是更容易吗?所以,总结一下,个人来说,在下一个五年左右,我会投资我的时间去学习C/C++而不是 Rust。C++是一个工业标准。程序员们已经习惯用它去解决巨量的差异化的任务超过 30 年了。至于 Rust 和其他类似的 - 他们仅仅是奇怪的玩具带有模糊的优点。从 2000 年开始人们已经假设 C++ 很快就会死掉,但自从那时开始起,C/C++并没有变得少用。相反的,事实上,它演进了(C++11、C++14),新的工具发布了(举例说 Clion 和 Clang),并且空间巨大。
一个 C++ 程序员从来不会很难找到一个工资不错的工作,必要的话,可以很快的学会 Rust。但相反的情况非常非常不会发生。顺便说一下,找工作时,语言的选择从来都不是唯一和最重要事。另外,一个熟练的C/C++程序员可以容易的学习 PostgreSQL's或者 Linux 核心代码,接触现代的强大的开发工具,并且有一大堆的书和文章在手(比如说 OpenGL)。
所以,关心一下你的健康吧,不要浪费你的时间 - 你只有比你想象的更少的时间!