第一章 Rust 语言简介

本贴最后更新于 1115 天前,其中的信息可能已经渤澥桑田

1 Rust 语言的发展历史

Rust 是一种采用过去的知识解决将来问题的技术。

Rust 是一种快速、安全的编程语言,由 Garydon Hoare 于 2006 年创造和发布。最初,它仅仅是一个个人项目,而后由 Mozilla 研发团队和许多开源社区成员共同维护和开发。2020 年,Mozilla 宣布成立 Rust 基金会的计划,新的 Rust 董事会将由来自 AWS华为谷歌微软Mozilla 的董事和 5 名来自项目领导层的董事组成,目前 Mozilla 已将所有商标和基础设施资产转移到了新的 Rust 基金会。

为什么起名为 Rust?

这个名字包含了 Garydon Hoare 对这门编程语言的预期。在自然界有一种名叫锈菌(Rust Fungi)的真菌,它们寄生于植物中,并引发病害,被称为“本世纪最可怕的生态病害”之一。这种真菌的生命力非常顽强,在其一生中可以产生多达 5 种孢子类型,这 5 种生命类型还可以相互转化。如果用软件术语来描述这种特性,那就是“鲁棒性极好”;Rust 也有铁锈的意思,暗含“裸金属”之意,这表达了 Rust 的系统级编程语言的属性,它具有直接操作底层硬件的能力。

Rust 项目开发的初衷是为了解决在当时使用 C++ 语言编写的 Gecko 项目中出现的内存安全问题(GeckoFirefox 浏览器所采用的引擎)。当时 Gecko 项目组正在尝试为这个引擎编写 CSS 多线程解析代码,以便充分利用现代 CPU 所提供的优秀并发性能。但是他们失败了,因为使用 C++ 编写的代码非常难以理解和维护。这个时候,Rust 诞生了,并且 Mozilla 同时还使用 Rust 编写了 Servo(一个新的浏览器引擎)项目。在这个过程中,Servo 项目组同时也向 Rust 项目组反馈了意见,这反过来又影响了语言的演变。

在过去的十年里,Rust 是最受开发人员喜欢的语言之一。Rust 逐渐开始在游戏开发、嵌入式设备、Web 编程、网络、开发者工具以及其他软件领域发挥重要作用。根据微软 GitHub 上的数据,Rust 采用率在 2019 年增长了 235%。

2 Rust 语言的设计哲学

Rust 项目组认为,未来的互联网除了关注性能,还一定会高度关注安全性和并发性。作为一款系统编程语言,Rust 语言的设计目标是:

  • 必须保证内存操作绝对安全。
  • 避免引入重量级的运行时组件,不引入不必要的性能损耗。
  • 包含优秀现代编程语言的特性,让代码更容易编写、维护、调试和分发。

2.1 内存安全

什么是内存安全呢?简单来说,就是在程序的运行期间,不会发生内存访问的错误。一般来说,发生以下几种情况时,就产生了内存访问的错误:

  • 引用了空指针,或者悬挂指针
  • 使用了未经初始化的内存
  • 缓冲区溢出
  • 重复释放某块内存

自 20 世纪以来,内存安全已经成为了重量级软件项目的巨大绊脚石。因此,内存安全是 Rust 要保证的重中之重。要在没有运行时系统帮忙的前提下实现内存安全,必须要做到以下两点:

  • 使用强类型系统,这样编译器可以帮助程序员侦测不安全的代码。
  • 对每个类型,明确地约束其行为。

Microsoft

根据微软安全响应中心提供的数据,所有微软年度补丁中约有 70% 是针对内存安全漏洞的修复程序。这样高的百分比是因为 Windows 和大多数其他微软产品主要使用 C 和 C++ 编写,这两种**“内存不安全”(memory-unsafe)**的编程语言允许开发人员对内存地址进行细粒度控制,并且可以执行代码。管理内存执行的开发人员代码中的一个漏洞可能导致一系列内存安全错误,攻击者可以利用这些错误带来危险和侵入性后果,例如远程代码执行或特权提升漏洞。

Python 3.9.1

2021 年 2 月 19 日,MicrosoftRedHat 对其用户发出了安全警告,称现有的 Python 解释器在执行 C 代码时存在安全隐患,容易被攻击者远程执行恶意代码,让操作系统崩溃。据悉,该漏洞是由于 Python 解释器使用了不安全的 C 库函数 sprintf,其可能导致缓冲区溢出。

Rust 语言采用三大编译期特性来保证程序运行时的内存安全:所有权借用生命周期,这被称为内存安全三大件。Rust 编译器可以在编译期对代码进行检查,看其是否满足内存安全模型,并指出隐含的内存安全问题,且提供精准的解决方案。

2.2 零成本抽象

除了内存安全性,Rust 还追求极致的运行时性能。现代软件项目中,为了管理软件的复杂性,必须提出良好的抽象来组织数据。然而,对于现有的很多编程语言来说,这种抽象都是以牺牲运行时性能作为代价的。例如,JavaC# 等语言为了实现其抽象,往往会在其对象中添加一些“佐料”,这会占用一定的内存空间;并且,它们的对象全部放在堆内存中,当程序产生大量的中间对象时,频繁地堆内存分配操作会占用大量的时间。

与之相反,Rust 使用泛型特性系统来实现抽象,它至多在每个对象中填充少量的字节来保证内存对齐,并且提倡在栈上构建对象。所有这些操作都可以在编译期直接翻译为高效的汇编代码,不存在任何运行时期的开销。

2.3 实用性

由于历史原因,传统的 C/C++ 在面对大型软件项目时不可避免地存在一些问题:

  • C/C++ 在设计上存在未定义的行为。部分 C/C++ 代码的行为并没有在其语言规范中指出,其执行结果完全取决于编译器的实现。例如,代码 i = (i++) 是一个未定义的行为,因为规范中并没有指出应该先执行自增还是先执行赋值。
  • C/C++ 没有统一的错误处理机制。日常开发中一般会有三种非正常情况:失败错误异常。但是像 C 这种面向过程的语言,开发者只能通过返回错误码,或者 goto 语句的方式进行错误处理,并且没有统一的处理格式;而 C++ 虽然引入了异常处理机制,但是没有专门提供能够有效区分正常逻辑和错误逻辑的语法,开发者必须将所有非正常情况全部当作异常去处理,并且由于异常可能会向上传播,编译器需要添加额外的二进制码来实现栈展开,使得二进制文件的体积增大,且引入了运行时的性能损耗。
  • C/C++ 难以共享代码。在大型项目中引入第三方的 C/C++ 代码是比较困难的,因为这些第三方代码可能采取了与当前项目完全不同的构建和打包方式;即便这些代码可以在修改后编译,也未必能够成功地整合到当前项目当中,这是由于第三方的二进制文件可能存在与当前项目不兼容的 ABI。

Rust 为上述问题提供了一套完整的解决方案。

3. Rust 代码如何执行

Rust 语言从诞生伊始就考虑了移植性问题。与 C/C++ 语言一样,Rust 编译器是一个编译前端,它的工作是对 Rust 代码进行词法分析、语法分析、类型检查、生成中间代码、对中间代码进行机器无关优化等工作;Rust 使用开源项目 LLVM 作为编译器后端代码生成框架,这使得 Rust 代码可以兼容多种目标平台,基本上可以做到一次编写,到处运行。当开发者实在需要进行平台相关的工作时,也可以通过第三方的程序包,或者是调用 C 函数的方式完成工作。

值得注意的是,从 Rust 1.12 版本开始,Rust 编译器前端会将抽象语法树处理为 MIR(Middle IR),这是一种中间表示形式,可以帮助编译器前端实现增量编译,并且为 LLVM 提供充足的代码优化信息。最终,MIR 会被处理为 LLVM IR,并最终被 LLVM 处理为目标平台上的机器码。

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    57 引用 • 22 回帖 • 2 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...