InfoQ中文站特供稿件:Rust编程语言的核心部件

本文为InfoQ中文站特供稿件。首发地址为: http://www.infoq.com/cn/articles/rust-core-components 。如需转载。请与InfoQ中文站联系。

原文发表于2015年12月22日,现依据之前约定将其全文转发到我(Liigo)个人博客里。

Rust是一门强调安全、并发、高效的系统编程语言。无GC实现内存安全机制、无数据竞争的并发机制、无执行时开销的抽象机制。是Rust独特的优越特性。它声称攻克了传统C语言和C++语言几十年来饱受责难的内存安全问题,同一时候还保持了非常高的执行效率、非常深的底层控制、非常广的应用范围,在系统编程领域具有强劲的竞争力和广阔的应用前景。

从狭义的角度说。Rust编程语言。就是其语言本身,一份以人类语言描写叙述的计算机编程语言的规范文档。

然而单单语言本身,仅具有理论价值;要发挥其有用价值,往往还要有编译器、标准库、执行环境等一系列配套设施。共同组成一套完整的生态体系。

从广义的角度说,Rust编程语言包括了:语言规范(reference)、编译器(rustc)、执行时(runtime)、标准库(std)、核心库(core)、库(crates)、包管理器(cargo)、社区(communities)等等。

本文将比較具体的介绍广义上的Rust编程语言各个组成部分。

语言规范

Rust语言规范规定了Rust编程语言的语法和语义。跟其它语言规范一样。充满枯燥的文字。真正愿意通读下来的人非常少。大多数人通过0基础教程学习语言的基本的语法和语义。仅在必要时翻阅或查阅语言规范的局部内容。(严格来说。Rust眼下提供的这份文档并不算是语言规范文档(specification),而仅仅仅仅是參考文档。)

编译器(rustc)

官方的rustc是眼下唯一的Rust编译器(之前的rustboot编译器早就被废弃了)。它负责把Rust源码编译为可执行文件、Rust库(crates)或其它库文件(.a/.lib/.so/.dll)。

  • rustc是跨平台的应用程序,其可执行文件名称是 rustc (for Unix/Linux/…) 或 rustc.exe (for Windows),最基本的命令行调用方法是 rustc hello.rs

  • rustc具有交叉编译功能,能够在当前平台下编译出可执行于其它平台的应用程序和库(但须要事先编译或安装目标平台的工具链)。

  • rustc採用LLVM作为编译器后端,具有非常好的代码生成和优化技术。支持很多目标平台。

  • rustc眼下使用gcc作为链接器(同一时候也执行时依赖glibc执行库,今后可换用MUSL静态库,相关开发工作在进行中);今后在Windows平台将支持使用MSVC作为链接器(相关开发工作在进行中)。

  • rustc编译出来的程序,支持用GDB和LLDB调试执行。用户不须要更换自己已经熟悉的调试工具,Rust没有也不须要自己专属的调试器。

  • rustc是用Rust语言开发的,并且是开源的,最新源码在这里:https://github.com/rust-lang/rust/tree/master/src/librustc

执行时(runtime)

在没有明白上下文的情况下,执行时(runtime)通常可被理解为“执行时库(runtime library)”或“执行时损耗(runtime overhead)”。

以下就这两种情况分别阐述。最后得出的结论是:Rust能够没有执行时库,且仅有非常小的执行时损耗。

执行时库(runtime library)

编程语言的执行时库,通常理解为。其编译出的可执行程序在执行时必须依赖的非操作系统本身的动态库。比如C程序必须依赖msvcrt或glibc,Java程序必须依赖JRE。VB程序必须依赖msvbvm,易语言程序必须依赖krnln.fne/fnr,等等。由于C执行时库往往跟操作系统紧密集成(尤其是类Unix系统),能够觉得C执行时库是操作系统的一部分。进而觉得C没有执行时库(当然这里见仁见智)。

假设认同这一点。那么。经过静态编译生成的Rust程序。执行时仅依赖C执行时库。也就能够觉得没有执行时库了。

即使不认同这一点。等以后Rust支持了静态链接MUSL库(同一时候抛弃掉glibc)。依旧能够做到没有执行时库。当然,动态编译的Rust程序中执行时还是必须依赖标准库libstd-*.so等动态库的,这是给予程序猿的额外可选项。

说Rust“能够”没有执行时库。就是说执行时库没必要的,程序猿拥有选择权(而不是被迫必须接受执行时库)。

那为什么说没有执行时库是一个优势呢?由于执行时库本身也有平台依赖性和/或执行时依赖性,有执行时库就意味着,你的程序仅仅能在执行时库所支持的平台下执行,也就是说它限制了程序的部署平台。

而执行时库支持哪些平台并非程序猿个体所能决定的。

就算执行时库官方开发商决定向新的平台移植。也往往受诸多因素干扰,比如十多年前试图将JRE移植到手机平台时就破费周折,甚至不得不大幅删减功能、人为制造了残缺不全的手机版JRE。再试想,在一个没有网络系统、没有文件系统,甚至没有操作系统的嵌入式平台上,你有可能在上面跑JRE环境吗?做梦。

没有了执行时库。程序的全部代码都是程序猿可控的(至于标准库的影响,下文将会谈到)。

(更宽泛地说,执行时库居无定形。未必一定以独立动态库的形式存在,它也可能隐身于标准库甚至是可执行文件内部。

仅仅要它给程序本身带来了额外的且无法消除的明显的依赖和不可忽略的执行时损耗,我们就通通觉得它是执行时(库)。反过来说。假设执行时(库)的执行时损耗小到一定程度,且没有带来额外的执行时依赖,我们甚至能够觉得它不是执行时(库)。此中斟酌。见仁见智。)

执行时损耗(runtime overhead)

程序的执行时损耗。是指程序在执行过程中所必须付出的额外的代价。比如Java的虚拟机、C#的垃圾回收器、脚本语言的解释器等等。这些子系统本身在执行时都会消耗数量可观的内存和CPU。影响程序和系统的执行性能。而Rust没有虚拟机、垃圾回收器和解释器。所以没有这类执行时损耗。

此外,内存管理、栈管理、调用操作系统API和C库等各种情况下,都有可能产生额外的执行时损耗。

Rust执行时须要每个函数执行morestack检查栈溢出(morestack已被取消),为了内存安全这是“必需的”检查。而以C语言的思路去看可能就是“额外的”损耗。不管怎样这项执行时损耗非常小。Unwinding仅发生在panic之后,不视为执行时损耗。

Rust採用jemalloc管理内存(也可禁用)。不仅没有执行时损耗。反而带来执行效率的明显提升。

Rust的Rc类型以引用计数管理对象内存,Arc类型以Atomic引用计数管理对象内存,这是较小的执行时损耗。

但假设程序猿不主动使用Rc/Arc类型,则无需为此付出额外的代价。

(Go语言的协程调度器。当然也有执行时损耗,但这在某种程度上是程序实现自身功能的必要,算不上“额外的”代价。假设不须要此功能则损耗非常小,故本文作者不视其为执行时损耗。

而其通过channel共享内存、管理逐步连续增长的栈、调用C库和系统API,则被视为执行时损耗,由于这些都是“非必要的”损耗,并且损耗还不小。)

(Java的JIT编译器在执行时把字节码编译为机器码,算不算执行时损耗呢?损耗肯定是有的,但仅在特定条件下触发,且其带来的收益可能远大于损耗。是提升执行性能的必要步骤。故本文作者不觉得它引入了“额外的”代价,不视其为执行时损耗。而Java的虚拟机和垃圾收集器,显然是突出的执行时损耗。)

标准库(std)

Rust的标准库,为绝大多数的、常规的Rust程序开发提供基础支持、跨平台支持。是应用范围最广、地位最重要的库(没有之中的一个)。其规模居中,既不像传统C和C++标准库那么简陋,也不像Java和.Net标准库那样包罗万象。

Rust标准库的内容大致归纳例如以下:

  • 基础的接口性数据类型

    如 Copy, Send, Sized, Sync, Drop, Deref, Clone, Iterator, IntoIterator, Debug, Display, Option, Result, Error, Eq, Ord, Fn, Cell, Hash 等等。当中多数都被包括在 std::prelude 内。

    这些简明扼要的类型,构成了Rust生态系统的基石。假设标准库不提供这些类型。让第三方库各行其是的话。整个生态系统将非常难形成合力。

  • 基础类型操作接口

    如 bool, char, i8/u8, i16/u16, i32/u32, i64/u64, isize/usize, f32/f64, str/array/slice/tuple/pointer 等基础类型数据的操作接口及事实上现。

  • 经常使用的功能性数据类型

    如 String, Vec, HashMap, Rc, Arc, Box, CString, OsString, SipHasher 等等。

    满足常见的、经常使用的,或特定的功能需求。

  • 经常使用的宏定义

    如 println!, format!, assert!, try!, panic!, vec!, thread_local!, file!, line!, include! 等等。

    基础的或核心的宏。当中某些宏是借助编译器实现的。

  • 跨平台的I/O相关的系统功能

    如 std::io, std::fs, std::path, std::env, std::process 等等。

  • 跨平台的网络/多线程/同步相关系统功能

    如 std::net, std::thread, std::sync 等等。

  • 其它的不跨平台的操作系统相关功能

    如 std::os。为各主流操作系统分别提供了专门的操作接口,便于实现系统特有的功能调用。

  • 底层操作接口

    如 std::mem, std::ptr, std::intrinsics 等。操作内存、指针、调用编译器固有函数。

  • 其它等等

核心库(core)

Rust核心库,能够理解为是经过大幅精简的标准库。它被应用在标准库不能覆盖到的某些少数特定领域,如嵌入式开发。

前面提到过,标准库应用范围非常广,为绝大多数应用程序提供支持。可是在嵌入式开发、操作系统开发、裸金属(bare metal)环境下,标准库就无能为力了。主要有以下两个原因导致标准库的应用范围受到一定的限制:

  • 标准库的“跨平台”是指“跨主流操作系统平台”,也就是跨 Windows、Unix/Linux、Mac/OSX 等少数几个操作系统。

    标准库内有相当数量的API(如文件、网络、多线程等)必须依赖操作系统提供的接口,到了非主流系统尤其是嵌入式系统环境下,标准库失去了底层系统的支撑根本就不可能工作。

  • 标准库内有相当数量的API(如String/Vec、Box、panic等)依赖内存申请和释放功能,可是在操作系统开发、裸金属(bare metal)环境下,要么不存在这些功能。要么须要自己开发。

这些限制对Rust标准库来说事实上并非问题。跟世界上大多数编程语言的标准库一样,为主流系统的主流应用开发提供丰富的功能支持,才是最重要的。

假设单纯为了提升应用范围砍掉操作系统相关的功能,那标准库也大概成了空壳子,功能性和有用性大打折扣,彻底失去了标准库的价值——谁能接受一个连文件、网络、多线程功能都没有的标准库呢?

Rust的选择是,在标准库之外。再单独提供一个核心库,重点应对嵌入式应用开发。核心库不依赖不论什么操作系统,也不提供文件/网络/多线程/内存申请释放相关的不论什么功能,因而可移植性更好、应用范围更广。当用Rust开发一个操作系统或硬件驱动或嵌入式应用时,你总不能指望去调用别的主流操作系统接口吧?那显然是不切实际的。所以对核心库来说。它缺少的那些OS相关功能原本就是多余的。

在代码开头写上 #![no_std] 就代表放弃标准库。而使用核心库。核心库里面有:基础的接口性数据类型(參见上文。下同)、基础类型操作接口、经常使用的功能性数据类型、经常使用的宏定义、底层操作接口等,并且跟标准库API差点儿是全然一致的;再配合alloc库(或自己定制的alloc库)又有了内存申请释放功能;再加上collections库。String/Vec/HashMap等也有了。

事实上从内部实现来说。标准库里的某些功能正是来源于核心库(以及alloc/collections等)。

库(crate)

把多个Rust源码文件(后缀名.rs)放一起编译出来,就得到一个库。库通常以静态库.rlib或动态库.so/.dll的形式存在。我们称Rust库为crate,就像别的语言把库称为library或package差点儿相同一个意思。仅仅是习惯上的命名不同。

库是Rust程序猿共享代码和功能的基本单元。

编写应用程序和软件,无非就是综合利用各种库,官方的库、自己的库、第三方的库,调用它们提供的接口(API)。再融合自己的业务逻辑。终于达成目的。

在已经编译或安装了某个库xxx的前提下,要想调用这个库。需首先在源码首部增加这么一行代码:

extern crate xxx;

我们不须要像Java操心CLASSPATH一样操心Rust库的载入路径,由于我们有Cargo(以下会讲到)。由于我们有静态编译。

眼下Rust已经有了大概3000多个公开的第三方库,全部集中在 crates.io 站点上(以下也会讲到)。这些库绝大多数都是Github上面的开源项目。

我好像极少听到有谁公布二进制的库(而不是公布源码)。

包管理器(Cargo)

Cargo是Rust官方提供的包管理器(package manager),相似于Java界的Gradle。Cargo负责下载库源码,分析库的依赖项,下载依赖项的源码,再分析依赖项的依赖项,如此这般,终于把他们逐个编译出来。一句话,就是处理下载(源码)、依赖(第三方库)、和编译(生成库或可执行文件)。

有了Cargo,不管多复杂的项目,不管有多复杂的依赖项,也仅仅需在项目根文件夹下执行这么一条命令:

cargo build

Cargo包管理器跟crates.io站点形成了完整的生态系统。

crates.io就是一个中心仓库,全世界差点儿全部的Rust项目都被整合在此仓库中。每个项目都包括了一个Cargo.toml的配置文件,指定了自身的依赖项。

Cargo就是环绕Cargo.toml开展工作的。

在C和C++的世界里。假设一个开源项目没有不论什么依赖,往往会被当作一项长处。由于大家都知道。编译带有依赖项的源码项目是非常麻烦的。尤其是当依赖项又有依赖项的时候,尤其是当依赖项的版本号号又不明白的时候。几十年了,都没出现一个被广泛接受的基于版本号的依赖管理和编译工具,颇为遗憾。

Rust不一样,它一開始就有了Cargo。

Cargo是一个令人骄傲的优秀的工具。

它不仅是一个工具。更是一个生态系统。

社区(communities)

Rust有相当庞大的社区。仅參与开发Rust系统本身的开发人员就多达1300人,并持续增长。这类开发人员中,以Mozilla公司员工组成的约10人团队为核心,以来自世界各地的贡献者为辅助。

採用Rust开发应用的开发人员人数很多其它,但难以统计数量。当然,作为新兴语言,Rust社区规模相对Java、Python社区而言还稚嫩的非常,发展潜力无限。

Rust开发人员活动轨迹主要集中在Github站点、IRC在线聊天室、Reddit论坛和Rust官方论坛中。此外,环绕某些颇具雄心的项目还各自形成了独立子社区。如ServoPistonMaidSafeRedox等。

源码仓库、设计开发讨论区:

- https://github.com/rust-lang/rust

- https://github.com/rust-lang/rfcs

- https://github.com/rust-lang/cargo

- https://internals.rust-lang.org

- https://client00.chat.mibbit.com/?server=irc.mozilla.org&channel=%23rustc

用户应用讨论提问区:

- https://www.reddit.com/r/rust

- https://users.rust-lang.org

- https://stackoverflow.com/questions/tagged/rust

- https://client00.chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust

官方站点:

- https://www.rust-lang.org

- https://www.rust-lang.org/community.html

- http://blog.rust-lang.org

- https://crates.io

- http://this-week-in-rust.org

中文用户讨论区

- http://rust.cc

总结

本文较具体的逐个介绍了Rust编程语言及其编译器、执行时、库、工具和社区等等核心部件,这些部件共同构成生机勃发的Rust生态系统。