引言

近日,微软云 Azure 的 CTO,Mark Russinovich 公开宣称“是时候停止使用 C/C 启动任何新项目”,并建议在需要使用 noc-GC 语言的场景下使用 Rust 编程语言。

要知道这位 Mark 老兄可是被称为“地球上最有才华的 C/C 程序员”,他这番言论也是惹得 C 之父 Bjarne Stroustrup 亲自下场对喷,称其为“喜欢新事物的高管”,却爱发表非常片面的言论。

先学c语言还是操作系统好(在系统编程领域)(1)

Bjarne Stroustrup

这桩公案暂不多说,再看另一位业界大神——Linus Torvalds,宣称要在 Linux 内核中加入 Rust 语言的代码。要知道 Linus 大神当年试用了 C 两周,就拒绝在 Linux 内核中使用 C ,并且把它喷了一辈子。

先学c语言还是操作系统好(在系统编程领域)(2)

Linus Torvalds

能够得到 Linus 大神的首肯,这当然是最好的背书。Linux 内核在发展三十年之后,终于在底层要迎来 C 之外的一门新语言。Rust 究竟有何魅力,让大神们如此倾心?它真的会替代 C/C 语言吗?

我们不妨从 Rust 的诞生之初说起。

Rust 的前世今生

话说还是在 2006 年时,Mozilla 公司的员工 Graydon Hoare 捣腾了个私人项目——Rust,应该就是想弄个工具方便自己用。没想到 Mozilla 公司看好此项目,于 2009 年开始赞助此项目。

Rust 也就从 Mozilla 走向了业界,2015 年发布了第一个稳定版本——Rust 1.0。这门系统编程语言,以其安全特性吸引了众多程序员的目光,逐渐流行起来。更是连续七年蝉联 StackOverflow 网站最受欢迎编程语言,可见其人气爆棚。

不过令人唏嘘的是,Mozilla 公司本打算用 Rust 重写 Firefox,但因为业绩不佳,不得不在 2020 年就将 Rust 核心开发团队大部分成员给裁了。

好在热心粉丝们又挺起了这个网红项目,2021 年时正式成立 Rust 基金会,Mozilla 也将商标和基础设施资产转移给 Rust 基金会。走上独立发展之路的 Rust,更是得到了谷歌等大佬的加持,在系统编程领域得以继续攻城掠地。

Rust 是一种什么类型的语言?很难给它贴一个标签,例如像 C 是过程式的,像 C 是面向对象式的。作为一门现代语言,它支持泛型、函数式编程、面向对象等特性。运行效率可媲美 C/C ,看起来是相当的实用。

Rust 的最大卖点,就是其具备 C/C 所没有的底层安全特性。那实际情况到底如何?让我们先从最关心的安全特性一窥其究竟。

先学c语言还是操作系统好(在系统编程领域)(3)

Rust 真的安全吗?

Rust 语言的安全性,其实是相对于 C/C 来说的。首先要强调一点,就是 C/C 并非“不安全”的语言,只是因为 C/C 的设计哲学,就是将控制权尽可能地交到程序员手中。

C/C 所具有的灵活性,让它们得以在做系统开发时能够控制几乎一切。但所有的安全性工作,都要由程序员承担。这就像是一件极其锋利的武器,但你却得光着膀子舞动它,会不会伤到自己,纯看个人水平了。

可问题就在于,团队中不可能个个都是大神,稍微不注意,就会出现悬垂指针、资源未释放、越界访问等问题。而这些隐患在 C/C 里是可以编译通过的,要等到运行起来才会出现最让人头痛的“Segmentation fault”错误。

Rust 的安全特性,就是解决了这些内存安全的问题,让程序员可以专心于功能实现。这就好像给程序员穿上了一件黄金锁子甲,武器锋利不减,舞动起来却再不担心会伤到自己了。

具体来说,当你写的 Rust 代码里存在安全隐患时,有的问题会在编译时就报错退出,有的则是在运行时产生一个panic崩溃提示。

我们可以看一个越界访问的代码示例:

fn main() { let animals = vec!["tiger", "panda", "mouse"]; //只定义了三个元素 let one = animals[4]; // 下标访问第五个元素 println!("animal: {:?}", one); }

这段代码的运行结果是产生了一个panic崩溃,并给出了明确的提示:

先学c语言还是操作系统好(在系统编程领域)(4)

若是在 C/C 中,越界访问可能并不一定会让程序崩溃,但崩溃时又会破坏堆栈信息,很难发现并调试。Rust 如此处理,显然让程序员可以更容易找到问题,并对这门语言充满信心。

Rust 内存安全探秘

Rust 有两大卖点,一是实现了不依赖于GC(Garbage Collection,垃圾回收)的内存自动管理;二是可以放心编写并发程序,号称“无畏并发”(fearless concurrency)。

就这两点,可以说是直击 C/C 程序员的痛点。想想看,既不用操心手工分配、释放内存,又不担心性能损失,还能安心玩高并发,多美。

做到这两件事,Rust 是基于三个语言特性:所有权、生命周期、借用。我们下面逐一进行解释。

所有权:在 Rust 中,每个值都是具有所有权的,变量通过移动语义获得值的所有权,从而成为所有者,且值只能有一个所有者。例如赋值语句,就是所有权的“移动”,而非数据的复制,这是迥异于 C 语言之处。

生命周期:值的有效可访问时间段,超出该时间段则资源会被释放。生命周期与作用域的概念并不同相同,它与所有权紧密结合,从而实现非 GC 方式的自动内存回收。

借用:既然值有严格的所有权与生命周期,那么程序中总会有共享访问值的要求,在不移动所有权的情况下,就可以使用“借用”的方式。它通过引用来实现,支持只读引用与读写引用。

最核心的这三个概念,从文字上看可能并不好理解。毕竟程序员的格言是 talk is cheap, show me the code。我们就从一个实例出发,学习这三条原则。

先学c语言还是操作系统好(在系统编程领域)(5)

Rust 开发上手

搭建 Rust 开发环境,我们以 Linux 环境为例,最简单的方式就是执行一句脚本:curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh,成功执行完毕,Rust 的相关编译工具也就安装好了。

我们先编辑如下这段代码,它定义了一个简单的结构体Student,还定义了一个函数show_info(),它接受一个Student类型的参数,并返回一个u64的值。在主函数中对Student类型的变量one,用show_info()函数调用了两次。

#[derive(Debug)] struct Student{ id: u64, } fn show_info(stud: Student) -> u64 { //函数的返回值是无符号长整型 println!("student id: {:?}", stud.id); stud.id //最后一个变量不加分号,表示返回这个值 } fn main() { let one = Student {id: 0}; //初始化变量 one let id = show_info(one); println!("return: {:?}", id); let id2 = show_info(one); println!("return again: {:?}", id2); }

保存源码至main.rs,然后执行rustc ./main.rs,结果却出现编译失败提示:

先学c语言还是操作系统好(在系统编程领域)(6)

如果你是刚接触 Rust,还按着 C/C 的套路来写这段代码,估计会有想掀桌子的冲动。凭什么这都编译不过?莫急,让我们根据上一节讲的三条原则来剖析一下发生了什么。

首先,标记代码第 13 行的蓝色字体,提示Student类型缺少trait Copy。struct类型默认没有实现Copy方法,所以实际发生的是所有权移动,而非数据复制。

接着,第 14 行蓝色字体的提示,就说明变量one在传递进show_info函数时,所有权发生了移动,one不再拥有值的所有权。标记第 17 行的红色字体表明,变量one已经失效,而值的生命周期是在show_info函数调用时结束,并不是在主函数中结束。

这对于 C/C 程序员来说,可能比较反直觉的,就是赋值语句与函数参数传递,除非特别声明,否则都是移动所有权。那么如何调整代码实现功能呢?这就可以通过“借用”技术来实现:

#[derive(Debug)] struct Student{ id: u64, } fn show_info(stud: & Student) -> u64 { //参数传入的是值的引用,而非移动所有权 println!("student id: {:?}", stud.id); stud.id } fn main() { let one = Student {id: 0}; let id = show_info(&one); //传入值的引用 println!("return: {:?}", id); let id2 = show_info(&one); //传入值的引用 println!("return again: {:?}", id2); }

代码修改了三处,show_info()的参数修改为& Student,调用时传参修改为&one。这样编译通过,&符号表示引用,可以理解为类似于 C 语言中的引用类型。

引用值不发生所有权转移,也不改变值的生命周期。仔细体会一下,这种严格的规则,的确让内存自动管理变得简单了。示例代码中实现是只读引用,读写引用要使用关键字mut,如何在show_info()里修改Student::id的值,就留给各位去思考与实践吧。

还有更多办法解决所有权问题吗?

《Rust 实战》这本书里提供了四条原则:

结语

至于说到 Rust 是不是能完全替代 C/C 语言,并让后者退出历史舞台?其实我倒并不这么认为,在相当长的一段时期内,C/C 仍然会拥有许多开发者,并且在语言特性上也会不断发展。

Rust 最大的意义,就是给系统编程提供了一种新的可能。它也获得了业界众多大神的支持,一定会有更多像 “Rust for Linux” 这样成功的项目不断涌现。

对于程序员来说,空口争论哪门语言才是最好的,这毫无意义。重要的是去学习、尝试使用新的编程语言,改善自己的工作绩效,提高生产力,这才是正道。

让我们重温一下 Rust 的官方口号:一门赋予每个人构建可靠且高效软件能力的语言。

,