传教文:Rust 大法好

Rust 是一门设计得非常不错的语言。我在 Twitter 上经常提起它。现在发布了 1.0 Alpha 测试版,是时候开始安利了!

rust game

不好意思图贴错了,

rust-lang

简介

Rust is a systems programming language that runs blazingly fast, prevents almost all crashes, and eliminates data races.

这是网站上的宣传语。作为一个系统级编程语言 Rust 并没有 .NET 或者 JVM 那样厚厚的 Runtime,并且有和 C 一样底层编程的能力。

并且非常快,速度和 C++ 差距不大,并且把 JVM .NET 甩在后面。它通过编译时检查来保证整个程序的正确和安全性,所有资源都会被回收,并且没有 GC 开销。在 Rust 中,一个变量的生存期(lifetime)是类型系统的一部分,内存何时回收会在编译时自动推算出来。

对比 C++ 它有更加干净的类型系统和语法,简洁的范型,在编译时被验证的 RAII,还有模式匹配之类独特的的函数式特性,以及强大的宏系统以及现代的模块管理。这里有一篇简单的比较

对比 Python、Ruby 等动态语言,可能没有那么灵活,但是先进技术下的静态类型语言别有一番风味,编译器会严谨地检查你的代码,杜绝一切低级错误发生。只要你心里装着:这个变量是什么类型,这个表达式是什么类型……你就会愉快地写程序,很多时候只要程序能编译出来,那么就能良好的运行了。

这一篇文章讲了语言本身的目标。和 C++ 一样,Rust 反对一个语言遵循一种教条的范式,你可以用它进行基本无副作用的函数式编程,也可以 OOP 或者过程式编程,利用宏,更多的范式也可以支持。没有多少的语法糖扰乱视线,比起一门炫酷的语言,Rust 更倾向于成为更现代化的工业级编程语言。

P.S. Rust 以前曾用过一个宣传语:“像 Haskell 一样类型安全,像 Erlang 一样并发,像 C++ 一样的性能。”。

特性概览

语言网站上的例子很好,而且能在网页上编译运行,只是用到的特性比较少。

稍微说明一下 :: 操作符用来获取名字空间中的符号,模块和结构都有自己的名字空间,比如说 模块::结构::new(),new 函数是社区惯例的构造器函数。

上面是一个简单的程序,包括很多种有意思的特性。

最先声明的 Enum 是一个简单而特殊的数据类型,它类似于 Haskell 的 data 或者 C 中的 union,表示它可能是多种类型之一。List 这个类型可能是一个范型的 Pair,也可能是代表表尾的 Nil。(Rust 错误处理就是通过 enum 类型在标准库中优雅的实现的)

Pair 是序对,也就是拥有两个元素的元组(tuple),第一个元素的类型是范型的,用来存放数据,第二个元素是一个指向下一个 List 的 box 指针,它可能又是一个 Pair,也可能 Nil。

后面在 main 函数中使用这个数据类型。首先用 let 创建了一个变量 len 作为链表的长度。let 出来的变量是不可变的,在代码中试图改变它的值会触发编译错误。

然后用 let mut 声明了一个可变的变量:list,list 用于存放当前的链表头节点。它是可变的(mutable),也就是说修改 list 的值不会让编译器报错。(前提是类型相同)

对于创建链表,首先将一个空的表尾 Nil 作为表头(list),然后利用 for  i in range(0, len),进行了 42 次循环,每次循环都将 list 更新成一个指向新的节点的指针,并指向旧的节点。这样就能让链表不断的增长了。

然后开始遍历这个链表, loop 代码块代表一个无限的循环。循环内是一个赋值语句,等号右边的 match 说明了要对当前的 list 做模式匹配。在模式匹配中,判断当前节点是一个 Pair 还是 Nil:是 Pair 的话就输出当前值,然后返回节点指向的下一个节点;是 Nil 的话就用 break 结束整个循环。

模式匹配

模式匹配是非常强大的特性,有了模式匹配生活简直美好。没有模式匹配的人生是灰暗的,模式匹配,啊,模式匹配……

简单来说,模式匹配有点像 case 语句的超级威力加强版,下面是一些用法。

指针

box

新的节点都是在 Box::new 中创建的,Box::new 会在堆上创建一个对象并返回指向它的指针。如果将 box 指针传给了另一个函数或者放在了某个结构体中之后,当前的函数就不再拥有这个指针,如果继续试图访问这个指针将会触发编译错误;指针永远保证自己只有一个所有者。在所有者的作用域结束的时候,资源会自动释放。

举个例子就是张三把肥皂丢在地上让李四捡,之后张三就不能用这个肥皂了,因为肥皂是李四了,如果李四没把肥皂丢给别人,李四死了以后肥皂就会和他陪葬。

borrow

如果指针那么容易发生转移的话很多事都干不了,所以有一种 borrow 指针,能将对象的所有权借出。编译器会确保有借有还——举个例子是,如果张三扔给李四一个肥皂,李四用完了以后张三嗝屁了,那么肥皂何去何从?李四扔不回去得多寂寞?所以编译器会检查代码,如果张三在取回肥皂前嗝屁,程序将不会通过编译。

Rc

一个指针只允许有一个所有者不能满足所有情况,比如说想要创建一个树或图结构。标准库中有引用计数智能指针 Rc。类似 C++ 的 shared_ptr。

举个例子,浴室里的肥皂被很多人使用,假如他们一个一个得了某种传染病死掉了,肥皂的使用人数不断减少,最后为0,肥皂也就没人用了,肥皂也就扔了。

Rust 就是这样保证不出现内存泄漏和悬空指针的。不过假如有信心的话,在 unsafe 代码块中可以写出 C 一样不安全的代码。

无尽的测试

这门语言最大的黑点是一直在测试,从未稳定。语法经常大改,以前有四种特殊的指针语法,现在指针语法被砍得只剩两种了,各种特性也都会在社区广泛的讨论下不断轮来轮去,原本作为语言基本特性的绿色线程先是被移出标准库,最近直接被删除了,在发布 Alpha 的前几天,因为社区的讨论语言作出了一种大胆的修改,将 int 和 uint (unsigned int) 改名成 isize 和 usize。用来提示这两个数据类型的大小是依赖于机器的。

我觉得这语言最成熟的一点就在漫长而开放的测试期,测试期没有什么是不能改的,只要有用户发现一个坑就可以展开讨论,如果有道理的话就会改掉。只要三天两头不修改代码,旧的代码基本上不可能在最新的编译器上运行。

对于 Rust 来说,语言本身的设计水平是很高的。这种成熟得益于它的社区,作为一个非常不稳定的测试版语言来说,Rust 的社区非常活跃,Mozilla 的试验性排版引擎 Servo 以及 Rust 编译器自己就是用 Rust 写的,这是十万行+的大项目。而且很多人用它写自己的中小项目,Rust 在游戏开发备受期待,因为它有很多超越 C++ 的现代化特性并且有和 C++ 一样的性能。有游戏引擎和库绑定(PistonSDL2),有人正在用 Rust 复刻 Doom,等等等等。同时也有许多 Web 方面的库 (1 2 3),还有人用它来做平铺式窗口管理器,这些都是很小一部分,可以在这里看到一些流行的项目

说这些就是表示,这门语言在没有兼容性包袱的同时,拥有活跃的社区和使用者,那么就可以在广泛讨论之后大胆改掉任何社区认为不正确的语法。这是一种很棒的模式。

一旦 Beta 版发布,以后的代码就会保证兼容性了——直到 2.0 版本。Rust 编译器及整个社区对版本号的统一约定是 SEMVER 2.0,根据此版本规范 Rust 2.0 是理论上打破向后兼容性的版本,但是这不在计划之内。

谁适合 Rust?

我觉得我上面写得又臭又长一串根本不能吸引人,搞不好有反效果,现在我简短地列举一些可能会喜欢 Rust 的人。

  • 嫌 C++ 太杂乱的人
  • 对性能有很大需求的人
  • 想要尝试接触新事物的人
  • 觉得 Ruby、Python 等语言有的时候太灵活的程序员
  • 喜欢写宏的程序员
  • ……

我感兴趣,怎么去学呀!

这两个都是很棒的官方教程,由原 Ruby 社区的某大牛维护,入门完全没问题,唯一的缺点是最近加入的高级特性并没有文档。

中文翻译不可避免的落后于官方,所以推荐英文为主,中文为辅。

传教文:Rust 大法好》上有18条评论

  1. oopsing

    1. 没有尾递归优化吧?
    2. 线程模型,没有了green thread,大量并发性能咋整?
    3. 继承只能继承trait,数据咋整? 组合?

    回复
    1. 酿泉

      1. 遗憾,目前没保证尾递归优化,但是为了尾递归优化保留了一个关键字。并且实际上代码是有尾递归优化的。LLVM 做了这个工作。
      2. 我不懂多线程这些,但是绿色线程被取消是经过总总考虑的,待会我贴链接。
      3. 没有数据的继承,和 Go 一样。继承除了引入复杂度以外其实用处真的不大。

      回复

发表评论

电子邮件地址不会被公开。 必填项已用*标注