Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

22 Jul 2016

从C++的角度看Rust

C++是变得越来越复杂了。从一开始作为C的预处理器,让C支持面对对象的语法,到现在,C++支持的编程范式(paradigm)也越来越多,比如可以用C++进行泛型(generics)编程,甚至并发(concurrency)编程。从C++11开始,C++更是对自己从语法上进行大刀阔斧的改进,语言标准不断更新,随即推出C++14作为C++11的修订,预计马上要推出一个新的修订:C++17

编程语言和计算机硬件打交道的。个人觉得,计算机硬件的发展是语言更新背后的推力。谈起编程,现在的程序员跟以前的程序员在感觉上应该有很大的不同。在C被发明之前,那时的程序员用汇编语言进行编程,除了让程序能够正常工作以外,大家主要思考的事情是如何让程序更节省CPU和内存。因为当时计算机硬件十分昂贵,这种计较可能会细化到一个时钟周期,一个字节的内存。在C刚被发明的时候,很多当时的程序员估计有这种看法,使用C是奢侈的,编译器生成的汇编代码肯定不如手写的汇编代码来得优美高效。

但是,事物的发展有一个惊人的规律,节俭始终不能长久,浪费才符合人性。于是在摩尔定律的作用下,计算机硬件的飞速发展。在CPU运算速度以G赫兹计,内存价格已成白菜价的今天,C语言都已经被认为是抠门的语言了,然后各种败家子似的脚本语言大行其道,典型的如Python,Ruby。在这些脚本语言中,CPU和内存都不是事,只要让程序员写代码的时候感觉爽就行了。因为程序员写代码的速度成为了一个瓶颈。

另外一个瓶颈是,摩尔定律到了看得见的头,一味增加CPU的速度已经不那么经济,只好把计算分散在多个处理器上并发执行,多核技术方兴未艾,君不见手机的CPU都有10个核了。所以,能够在未来存下去的语言一定需要两个特点,一是像脚本语言那种让程序员写代码爽的感觉,二是能够轻松无痛的支持并发编程。这也是C++从C++11标准开始夺命狂奔的原因,连C++的作者都感叹C++11像是一门新语言了(Surprisingly, C++11 feels like a new language)。

C++的劣势在于它的历史包袱太重。过去很多年,C++一直处于编程语言排行榜的前三甲,积累了大量的项目,这些都是C++不能抛弃的痛。所以即便C++想努力往前,也会被束手束脚,跑得不是那么快。一旦跑得不够快,就会被嫌弃,就会有新的挑战者出现。Rust就是其中的挑战者之一。

Rust由Mozilla Research推出。Mozilla的明星项目是开源的Firefox浏览器。以往,浏览器一直是C++的天下,没有人可以撼动。可是在多核和移动时代,浏览器的角色也迅速得在转变,几乎从以前渲染网页的图形界面变为一个多媒体中心。一个现代浏览器的代码数目已经达到天量(看看Quora上对Google Chrome的代码数目统计),这对C++带来了巨大的挑战,因为C++并不是一门内存安全的语言,常常一言不合就导致程序崩溃(C++程序员们,想想你们多少个日日夜夜花在查找内存错误问题上)。

要想在C++中进行内存安全的编码,常用的办法是有经验的程序员们约定一套编码规范(例子,High Integrity C++ Coding StandardC++ Core Guidelines),大家一起照着来,然后互相检查。通过这种方式,可以规避大部分问题。但是人毕竟会犯错失误,还是会有少量的漏网之鱼进入到代码中,导致灾难性后果(要么在关键时候崩溃,要么招致安全漏洞被攻击)。在天量代码的项目中,疏忽导致的错误就会成倍数增加,最终酿成大祸。

在处理内存安全问题,Rust沿用了C++那些编码规范,但不同的是,rust尽量让编译器而不是程序员来做编码规范检查。Rust通过重新设计语法达成一个目的,当程序员不遵循编码规范的时候,编译器能够发现错误并给出提示。

最后一点关于Rust性能问题。一般语言的编译过程分为前端和后端,前端将按语法规则(C++或Rust)写成的代码翻译成和具体语言无关的中间码,接着后端分析中间码并最终生成机器可以理解的语言。大部分的优化都是发生在后端,Rust借用C++的非常成熟的后端LLVM,所以后端优化这一块应该没有问题。对于前端的话,最重要的去除噪音,将代码中不必要的部分去除,使生成的中间码比较简洁,这要求语法设计上比较确定,没有二义性,这一点Rust也做得很好。所以,如果Rust能够在某些情况下性能超过C++,我一点都不感到奇怪(The Computer Language Benchmarks Game, Rust VS C++)。