为什么选Rust(Why Rust?)

在某些环境下-例如Rust的目标环境-比竞争对手快10倍甚至20倍是一个决定成败的事情.它决定一个系统在市场中的命运,就像硬件市场一样. —Graydon Hoare

所有计算机现在都是并行的… 并行编程是编程. —Michael McCool等,<Structured Parallel Programming>

TrueType解析器缺陷被国家级攻击者用于监视. 所有软件都是安全敏感的. —Andy Wingo

自从我们开始使用高级语言编写操作系统以来,系统编程语言在过去的50年中已经走过了漫长的道路.但是两个特别的问题被证明难以解决:

  • 编写安全代码很困难.在C和C++中正确管理内存尤其困难.几十年来,用户一直在遭受这种后果,安全漏洞至少可以追溯到1988年的莫里斯蠕虫.
  • 编写多线程代码非常困难,这是利用现代机器能力的唯一途径.即使是有经验的程序员也要谨慎处理线程代码:并发可能会引入广泛的新类错误,并使普通错误更难以重现.

进入Rust:一种安全,并发的语言,具有C和C++的性能.

Rust是由Mozilla和贡献者社区开发的一种新的系统编程语言.像C和C++一样,

Rust为开发人员提供了对内存使用的精确控制,并保持了语言的原始操作与其运行的机器之间的密切关系,帮助开发人员预测其代码的消耗.Rust共享了Bjarne Stroustrup在他的论文”Abstraction and the C++ Machine Model”中为C++所阐明的野心:

一般来说,C++实现遵循零开销原则:你不使用什么,你不需要付费.更进一步:你使用什么,你不能手写更好的代码.

对于这些,Rust增加了自己的内存安全和可靠性并发的目标.

实现这个承诺的关键是Rust新颖的所有权(ownership),移动(moves)和借用(borrows)系统,编译期检查以及精心设计的补充Rust的灵活的静态类型系统.所有权系统为每个值建立了明确的生命周期,使得核心语言不需要垃圾收集,并且启用明确但灵活的接口来管理诸如套接字(sockets)和文件句柄(file handles)等其它类型资源.移动将值从一个所有者转移到另一个所有者,借用允许代码临时使用一个值而不影响其所有权.由于许多程序员以前从未遇到过这种形式的这些特性,所以我们在第四章和第五章中对它们进行了详细的解释.

这些相同的所有权规则也构成了Rust可靠的并发模型的基础.大多数语言都将互斥锁和它想要保护的数据之间的关系保留在注释中;Rust实际上可以在编译时检查你的代码在访问数据时是否锁定互斥锁.大多数语言告诫你要自己确保你将数据结构提供给另一个线程后不要使用它;Rust则会检查你没有使用.Rust能够在编译时阻止数据竞争.

虽然Rust具有一些面向对象的特性,但它并不是一种面向对象的语言.Rust不是一种函数式语言,尽管它确实倾向于使计算结果的影响更明确,就像函数式语言那样. Rust在某种程度上类似于C和C++,但是这些语言中的许多习语都不适用,因此典型的Rust代码与C或C++代码并不十分相似.最好保留判断Rust是什么语言的判断,并在你熟悉语言后看看你的想法.

为了在真实环境中获得有关设计的反馈,Mozilla用Rust开发了一种新的Web浏览器引擎-Servo.Servo的需求与Rust的目标刚好匹配:浏览器必须性能良好并且安全地处理不受信任的数据.Servo使用Rust的安全并发性使整个机器能够处理在C或C++中不切实际的并行化任务.事实上,Servo和Rust一起成长,Servo使用最新的语言特性,而Rust则根据Servo开发人员的反馈而不断发展.

类型安全(Type Safety)

Rust是一种类型安全的语言.但是,”类型安全”是什么意思?安全听起来不错,但究竟是什么让我们保持安全?

以下是1999年C编程语言标准中未定义行为的定义,称为C99:

未定义行为(undefined behavior) 使用不可移植或错误的程序构造或错误数据的行为, 该行为本国际标准没有规定.

考虑下面的C程序:

  1. int main(int argc, char **argv) {
  2. unsigned long a[1];
  3. a[3] = 0x7ffff7b36cebUL;
  4. return 0;
  5. }

根据C99,因为这个程序访问数组a末尾之后的一个元素,它的行为是未定义的,这意味着它可以做任何事情.当我们在Jim的笔记本上运行这个程序时,它产生了以下输出:

  1. undef: Error: .netrc file is readable by others.
  2. undef: Remove password or make file unreadable by others.

然后它就崩溃了.Jim的笔记本电脑甚至没有 .netrc 这一文件.如果你自己尝试,它可能会做一些完全不同的事情.

C编译器为这个main函数生成的机器码恰好将数组a放置在返回地址之前的三个单词的栈上,所以在a[3]中存储0x7ffff7b36cebUL会改变main的返回地址,使其指向C标准库的代码中,该库查询一个 .netrc 文件以获得密码.当main返回时,执行不会在main的调用者中恢复,而是在库中这些行的机器码中恢复:

  1. warnx(_("Error: .netrc file is readable by others."));
  2. warnx(_("Remove password or make file unreadable by others."));
  3. goto bad;

在允许数组引用影响后续return语句的行为时,C编译器完全符合标准.未定义的操作不仅会产生未指定的结果:允许程序执行 任何操作 .

C99标准授予编译器全权委托,以允许它生成更快的代码.而不是让编译器负责检测和处理奇怪的行为,如运行到了数组的末尾,该标准使程序员负责确保首先不会出现这些条件.

从经验上讲,我们并不擅长这一点.在犹他大学学习期间,研究员Peng Li修改了C和C++编译器,使他们翻译的程序在执行某些形式的未定义行为时进行报告.他发现几乎所有程序都有问题,包括来自备受尊重的,将代码保持在高标准的项目程序.在实践中,未定义行为经常会导致可利用的安全漏洞.莫里斯蠕虫利用之前展示的技术从一台机器传播到另一台机器,这种漏洞利用今天仍然广泛使用.

根据这个例子,让我们定义一些术语.如果编写了一个程序,没有可能的执行可以表现出未定义行为,我们就说该程序 定义良好 ( well defined ).如果一个语言的安全检查确保每个程序都定义良好,我们就说语言是 类型安全 ( type safe ) 的.

精心编写的C或C++程序可能是定义良好的,但C和C++不是类型安全的:前面显示的程序没有类型错误,但表现出未定义的行为.相比之下,Python是类型安全的.Python愿意花费处理器时间以比C更友好的方式检测和处理超出范围的数组索引:

  1. >>> a = [0]
  2. >>> a[3] = 0x7ffff7b36ceb
  3. Traceback (most recent call last):
  4. File "<stdin>", line 1, in <module>
  5. IndexError: list assignment index out of range
  6. >>>

Python引发了一个异常,它不是未定义的行为:Python文档指定对a[3]的赋值应该引发一个IndexError异常,正如我们所看到的.当然,像ctypes这样提供对机器无约束访问的模块可能会在Python中引入未定义的行为,但核心语言本身是类型安全的.Java,JavaScript,Ruby和Haskell都是类似的.

请注意,类型安全与语言在编译时还是在运行时检查类型无关:C在编译时检查,不是类型安全的;Python在运行时检查,类型安全.

具有讽刺意味的是,主流系统编程语言C和C++不是类型安全的,而大多数其他流行语言都是.鉴于C和C++旨在用于实现系统的基础,委以实现安全边界和不受信任的数据接触的重任,拥有类型安全对他们来说似乎是特别宝贵的品质.

这就是Rust要解决的几十年来的矛盾:它既是类型安全的,也是一种系统编程语言.Rust旨在实现那些需要性能和对资源进行细粒度控制的基础系统层,仍保证了由安全类型提供的可预测性的基本水平.我们将在本书后面的部分中详细介绍Rust是如何管理这种统一的.

Rust类型安全的特殊形式对多线程编程产生了惊人的影响.在C和C++中,并发是很难正确使用的;开发人员通常只有在单线程代码已经无法实现它们所需要的性能时才会转向并发.但是Rust保证,并发代码没有数据竞争,在编译时捕获任何互斥或其他同步原语的滥用.在Rust中,你可以使用并发,而无需担心除了最熟练的程序员之外,其他人的代码都不能正常工作.

当你必须使用原始指针时,Rust有一个安全规则的逃出阀.叫做 不安全代码 (unsafe code),虽然大多数Rust程序不需要它.我们将在第21章中展示如何使用它以及它如何适应Rust的整体安全方案.

和其它静态类型语言一样,Rust的类型可以做的不仅仅是防止未定义行为.一个熟练的Rust程序员使用类型来确保值不仅安全而且有意义地使用,其方式与应用程序的意图一致.特别是,第11章中描述的Rust的traits和泛型提供了一种简洁,灵活且高效的方式来描述一组类型的共同特征,然后利用这些共性.

在本书中,我们的目标是让你领悟到,你不仅可以用Rust编写程序,还可以使用该语言确保这些程序既安全又正确,并能预测它们的执行方式.根据我们的经验,Rust是迈进系统编程的重要一步,我们希望帮助你充分利用它.