Rust Programming Language Study Guidelines.
导言#
最要紧的,我知道,这个《Rust 程序设计语言学习指导大纲》的名字听起来过于高大上了。但请放心,取这个名字的唯一原因,就是让其显得大气,于是乎您拿着这个小册子走到公共场合时,(或许)能获得很高的回头率,同时说不定也能提高您的学习热情。
Rust 诞生于 2015 年,距今已经过去了 9 年,是一个已经有一定历史的程序设计语言。如果您上网去搜索有关 Rust 的词条,那么您会看到各种各样的声音,评论说 Rust 是一门除了难学以外什么都好的语言。的确,有人评价 Rust 除了难学以外,具有其他所有的编程语言优秀特质。它的内存安全、并发安全、执行效率等都做到了无与伦比的程度;而且配合有高效或现代的 Cargo 包管理器,以及一整套符合 C 标准的编译工具链(Rust 官方编译器目前仍然基于 gcc GNU C Compilers)。它的全静态编译模式使得从开发到调试都特别简单;而且作为一门来自开源社区、面向开源社区的语言,这些特性让它更加适合大规模的基于版本控制系统(在 Rust Cargo 中默认是 Git)的合作开发。Rust 经过了 9 年的发展,虽然是一门较新的语言,但已经获得了相当多的认可,这其中包括大名鼎鼎的诸多企业,以及闻名遐迩的开源开发者(譬如 Linux 内核开发者 Linus Torvalds 已经着手准备向内核中添加 Rust 以逐渐代替部分 C)。上述名词您可能一个也听不懂,但请放心,本自然段概括的主旨是:Rust 绝对是一门好的语言。
“可是 Rust 它难学呀。我难道不应该像网上推荐的一样学会了 C 再来学吗?” 的确,如果您曾经接触过 C 语言或 C ++, 那么您学习 Rust 的时候将轻松很多,且这本小册子的第一章(《认识计算机》)您应该可以略过不看。然而之所以有学习 C/C++ 来帮助学习 Rust 这样的说法存在,是因为 Rust 的诸多项特性恰恰是为了解决 C/C++ 以及其他一些编译型的、略贴近底层的语言的一些诟病而提出的;而说 Rust 难学也恰恰因为这一点 —— 如果从未接触过底层编程的人来学习 Rust 会感到有很多特性是完全没有必要且无厘头的 —— 其实这是因为如果未接触过编程,或者只接触过顶层的编程(Javascript, Python 甚至 Scratch),对于计算机的理解是完全不到位的。而这本小册子将创新性地拒绝一上来就教读者学习 Hello World 怎么打印(其实读者稍后就会知道 Hello World 根本不需要写,因为 Rust 的新项目默认模板就是 Hello World),而是通过 1000 字左右的图文并茂的形式,将计算机工作原理中最重要的部分,简明扼要地传达给读者。同时在后续章节中也时时注意从原理入手。这样一来,即使从未学习过其他任何编程语言,凭借对于计算机的进一步了解,也能体认到 Rust 的卓越设计,获得更好的学习效果。
您可能还会有疑问,“为什么要学这一门我甚至都没听说过的语言呢?” 的确,Rust 因为难度大、应用场景相对较窄,被很多初学者所避之不及,并且认为像 Python, Javascript 这样的语言就完全足够满足日常需求,甚至可以用来谋生计。固然他们的说法没有错,但您需要意识到,Javascript 是一个面向万维网的编程语言,并且它最大的特征就是极其易学;如果您学完了 Rust,那么 Javascript 对于您来说几乎是不用学的。这里没有贬低任何这类脚本语言的意思,但事实就是这些更高级(我们刚刚说的这些高级与底层都是针对与硬件的距离来说的)的语言谁都可以学,谁都学得会,而且是高度逐新、高度依赖框架的,他们能提供的无论是就业市场还是开源世界里的地位的广度和高度都是非常有限的。且不了解当代计算机的工作原理,只是处理业务逻辑(Javascript 的重点),一旦出现底层的问题,也没有办法自己排查,还是会依赖于人。底层的事务不会因为语言高级就不存在了;相反,底层的业务总要有人来处理,这就是那些解释器和解释器开发者的任务;他们才能成为整个行业的中心。一些底层特性,比如指针,只是解释器像保姆一样帮你把它们封装、管理起来了;无论是效率还是灵活度,肯定比不上真人自己动手的。如果通过学习 Rust, 您能够对计算机有更加理性和准确的认识,那么无论是在生活还是在生产中遇到的很多问题都能用明晰的头脑解决,且学习其他语言也将更加容易;并且说不定您还能成为处理底层业务的大师,成为行业领先的人物。
此外,Rust 并不像很多人说的那样是用来 “写驱动程序和操作系统的”。相反,当今已经有非常多的企业将 Rust 应用于生产环境,用来运行它们网络服务器和其他数字基础设施。因为 Rust 具有两个重要特征:零成本抽象带来的高性能、现代内存和并发管理带来的运行安全。但如果不了解计算机基本原理,并且还像之前所说的那样,把脚本语言的解释器当作自己的保姆,那么这两点关乎整个计算机时代的稳定性的因素,将永远无法理解。
认识计算机#
本章需要的前置知识:基本的生活经验。如果遇到任何不懂的术语,且文中没有给出相应的解释,请直接跳过;这些术语对理解本章和本册子(暂时)没有帮助和理解的必要。
现代计算机看起来是一个高度复杂的设备;当然也确实如此。一台普通笔记本里的结构估计是人类目前为止制造的集成度最高的结构了。但无论如何,计算机都是一台机器,而且完全由人设计。科学家们不是没有考虑过将计算机设计得跟人脑一样复杂,以期实现和人脑一样复杂与巧妙的功能;早在上个世纪中期就已经有一群科学家尝试过了,但受限于当时的(和现在的)生理学认识的有限,连人脑最概括的原理图都是很难明确的。所以以冯・诺伊曼为首的一群科学家选择了另一条路,即设计一套超级简单的计算体系架构;毕竟只要能达成造一台通用计算机的目的就行了,也不要追求人脑级的复杂。于是乎,就诞生了 “超级简单” 的冯・诺伊曼架构。
好吧,可能也没有 “那么简单”。但大纲是很清楚的。冯・诺伊曼架构定义了过去和现在,以及未来的一切通用计算机的基本架构:运算器 (CPU)、输入输出 (I/O Bridge)、存储器 (Memory)。简单地说来,运算器是计算的部分、输入输出就是字面意思、而(存储器)内存就是负责存储东西的地方。千万不要闹笑话了,存储器是 RAM,也是一般大家说的 “内存条”。 而经常被搞混的是硬盘;硬盘属于输入输出设备中的一种!内存又称易失性存储或者 RAM ,断电后里面所有东西都会清空(要不然为什么叫易失性存储),但读取和写入速度非常快,而大小一般很小,一般(绝大多数情况下)不会用来存储文件。硬盘,是连接在上面那个图表中 “Bus”(常说的总线)的部分的一个输入输出设备,用来存放输入和输出的数据。内存和硬盘中虽然都可以存储东西,但最大的不同就是,硬盘是用来接受 CPU 和内存这个 “工厂” 的输出的结果,并提供输入进去的材料的设备,它就像是工厂的仓库;内存则是这个工厂里面的桌子椅子,给工作人员和各种工具提供暂时的存放空间、以及给待加工的材料提供一个台面来加工。为了避免混淆,这本小册子中所有的 “内存” 全部都使用 “主存储器” 来表达,而本册同时将会使用 “运行数据” 来称呼所有存在于易失性存储中的数据和指令,请读者注意。同时,工厂的比喻将会在本册贯穿,稍后您会知道原因。
而在 CPU 内部仍然有具备存储功能的组件。全宇宙适用的机械原理决定了存储设备的一个特性:容量越小、速度越快(忽略技术原理差异的语境下)。所以 CPU 内部具有的这些存储组件容量都非常小,最多不会超过几个 MB(当然了,这种大小已经是现代 CPU 提供的了;一些较老的教材可能说 CPU 中的存储组件大小只有几个字节)。还是工厂,如果说主存储器是加工台,可以把待加工的材料放在上面;那么这些 CPU 内部的存储组件就是工具箱,在极短时间内可以存放一些小零件,并且放在很 “顺手” 的地方,可以很快取过来。把东西放到加工台上(从主存储器中读取和写入)需要的时间,要比从工具箱里拿零件(从 CPU 内部存储读取和写入)的时间还要长好几十倍,尽管前者已经只有不到半毫秒。
这些 CPU 内部的存储组件,有它们自己的名字。一些叫作 “高速缓存” (cache),一些叫作 “寄存器” (register), 但读者不需要了解它们。一些教科书可能会说,编程的时候可以让程序要求将各类数据存在缓存、寄存器还是主存储器中 —— 请不要相信和实践,因为现代编程语言和现代操作系统,出于安全和稳定的考虑,都会无视程序的这类要求。Rust, 在编译时就不会给任何程序选择运行时数据存放位置的权利。所以我们也不会继续介绍和了解 CPU 内部的存储组件;读者只要知道有这回事就行了。对于程序而言,它们能看到的部分就只有单纯提供运算的 CPU, 单纯提供运行数据存储的主存储器和其他 I/O 设备(包括硬盘,打印机,摄像头,麦克风,扬声器...)。就好像工厂经理(操作系统)会告诉工人每个东西应该放在哪里;不会由着他们乱放。
CPU 提供运算的原理是非常复杂的。但大家总之知道它能够完成基本四则运算和其他一系列高级的函数等等;这些就是数学和数字电路设计的范畴了,我们虽然要贴近硬件,但也尚且不至于这样贴近。且笔者能力有限,这一部分就不过多赘述了。
I/O Bridge, 本文称为输入输出,或者 I/O 桥,连接了 I/O 总线和 CPU 以及主存储器,就像是工厂里的材料输入口和产品输出口。没有 I/O 桥,这个工厂的存在也毫无意义。I/O 总线听起来可能有点奇怪,但它的英文名很好的解释了一切,“Bus”,大巴车。的确,I/O 总线就像大巴车,载着数据从它们的处所(这就有很多可能了,有可能是另一个工厂的输出,也有可能是一个专门的设备)到工厂去;或者反过来。I/O 这个名字同时也会用得非常广泛,毕竟就是 input /output 的意思 (输入 / 输出)。每个工厂有进料口和输出口;那每台机器(程序)也可以有进料口和输出口。所以如果在编程时看到 I/O 用来形容用户的输入和显示在屏幕上的时候,不需要惊讶。
于是乎,我们的计算机系统漫游就结束了。这部分的内容细细讲来可以写成一本巨作(《深入理解计算机系统》推荐您阅读,如果想要深入理解计算机系统的话),但对于面向程序的学习来讲,这些已经绰绰有余啦。
P.S. 为什么将计算机比作工厂、程序比作机器的比喻这么常见,并且本文也会坚持使用呢?这是因为现代计算机的全名其实叫作通用计算机。换句话说,就是像孙悟空一样能够七十二变(而且还是同时变),可以变化成各种专用目的的机器 —— 其实就是在通用计算机诞生或在某特定领域投入使用之前存在的专用计算机(一般叫作专用设备,IC(集成电路)等等)。譬如,交换机就是典型的专用计算机,它不可编程,不可当成别的东西使用;但个人电脑就是通用计算机,您可以通过编程或者安装已有的程序把它变成电话、变成电子邮件收发机、变成交换机、变成路由器、变成服务器…… 随着通用计算机技术的成熟,以及不再需要像专用计算机一样每次都设计专门的集成电路,同时还为了方便地实现更多功能,越来越多的专用计算机设备已经被通用计算机替代。譬如,物联网设备如联网热水器,就通过一块单片电路板的通用计算机(单片机)进行控制;从某种意义上来讲,这种设备和个人电脑本质上是相同的!这样的比喻能够合理地成立,是因为它某种意义上描述了通用计算机诞生之前的世界 —— 也是大多数人所熟知且本能地能接受的世界。
下一章我们将会学习保留节目:用 Rust 写 Hello World. (但其实我们要写一个简单的字符串搜索小程序 —— 不要紧张,这很容易做到的。)