├── glossary └── glossary.md ├── learn_rust ├── README.md ├── dining.md ├── ffi.md └── guessgame.md ├── syntax_semantics ├── iflet.md ├── primtype.md ├── struct.md ├── vector.md ├── refborrow.md ├── associated.md ├── plugin.md ├── unsized.md ├── generic.md ├── aliase.md ├── unsafe.md ├── attribute.md ├── match.md ├── comment.md ├── drop.md ├── constatic.md ├── cast.md ├── operator.md ├── mutability.md ├── module.md ├── method.md ├── string.md ├── deref.md ├── if.md ├── function.md ├── loop.md ├── macro.md ├── binding.md ├── pattern.md ├── closure.md ├── traitobject.md ├── pointer.md ├── trait.md └── ownership.md ├── intermediate ├── README.md └── collections.md ├── basic ├── README.md ├── input.md ├── array.md └── compound.md ├── effective_rust ├── stackheap.md ├── conditianalcompilation.md ├── releasechannel.md ├── borrowasref.md ├── ffi.md ├── iterator.md ├── document.md ├── concurrency.md ├── test.md └── error.md ├── conclusion.md ├── getting_started ├── helloworld.md ├── install.md └── cargo.md ├── README.md ├── introduction.md └── SUMMARY.md /glossary/glossary.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /learn_rust/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/iflet.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/primtype.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/struct.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/vector.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/refborrow.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /syntax_semantics/associated.md: -------------------------------------------------------------------------------- 1 | 关联类型 2 | 3 | -------------------------------------------------------------------------------- /syntax_semantics/plugin.md: -------------------------------------------------------------------------------- 1 | 编译器插件 2 | === 3 | -------------------------------------------------------------------------------- /intermediate/README.md: -------------------------------------------------------------------------------- 1 | 这部分包含独立的章节互不依赖,各自专注于一个主题,你可以按任意次序阅读。 -------------------------------------------------------------------------------- /basic/README.md: -------------------------------------------------------------------------------- 1 | 这部分介绍 Rust 的句法和语义。每一章节描述一种 Rust 句法,最后总结所有章节完成一个猜谜游戏。 2 | 3 | 在读完基础部分,你就具备了进阶的基础并且能够写一些简单的程序。 -------------------------------------------------------------------------------- /effective_rust/stackheap.md: -------------------------------------------------------------------------------- 1 | #堆和栈 2 | 3 | ##内存管理 4 | 5 | ##栈 6 | 7 | ##堆 8 | 9 | ##参数和借贷 10 | 11 | ##复杂点的例子 12 | 13 | ##其他语言是怎么做的? 14 | 15 | -------------------------------------------------------------------------------- /conclusion.md: -------------------------------------------------------------------------------- 1 | 掌握了这本手册里的所有内容将为你的 Rust 编程打下坚实的基础。当然还有很多我们没有提及的内容,或者一些内容只是蜻蜓点水;有非常多的主题你可以自己去深入学习。[标准库](http://doc.rust-lang.org/std/)的 API 文档、[Rust by Example](http://rustbyexample.com/)、[crate.io](https://crates.io/) 都是非常有用的资源。 2 | 3 | 编程愉快! -------------------------------------------------------------------------------- /syntax_semantics/unsized.md: -------------------------------------------------------------------------------- 1 | 变长类型 2 | === 3 | 4 | 大多数类型长度是固定,比如 i32,编译器在编译的时候知道他是 4 字节。但有时候我们想要描述的对象大小是动态的,这时候我们就需要变长类型,比如 [T], 它代表一连串的 T,个数未知。 5 | 6 | Rust 能处理这些变长类型,但有一些限制: 7 | 8 | * 我们只能够通过指针来操作变长类型,例如 `&[T]` 9 | * 变量和参数不能是变长类型 10 | * struct 结构体里只有最后一个成员能够是变长类型。枚举类型不能有变长类型成员。 -------------------------------------------------------------------------------- /effective_rust/conditianalcompilation.md: -------------------------------------------------------------------------------- 1 | 条件编译 2 | 3 | Rust 提供了一个特殊的属性 #[cfg],使用它你可以通过传参给编译器来控制代码的编译,它有两种形式: 4 | 5 | #[cfg(foo)] 6 | 7 | #[cfg(bar = "baz")] 8 | 9 | 还有一些帮助: 10 | 11 | #[cfg(any(unix, windows))] 12 | 13 | #[cfg(all(unix, target_pointer_width = "32"))] 14 | 15 | #[cfg(not(foo))] 16 | -------------------------------------------------------------------------------- /getting_started/helloworld.md: -------------------------------------------------------------------------------- 1 | Rust 安装好了,我们开始写第一个 Rust 程序。我们遵循古老的传统,打印 Hello World 到屏幕。 2 | 3 | $ mkdir ~/projects 4 | $ cd ~/projects 5 | $ mkdir hello_world 6 | $ cd hello_world 7 | 8 | fn main() { 9 | println!("Hello, world!"); 10 | } 11 | 12 | 保存文件然后执行: 13 | 14 | $rustc main.rs 15 | $./main 16 | Hello, World! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [《Rust 编程语言》](SUMMARY.md) 2 | === 3 | 4 | 我知道这本手册已经有非常多的翻译版本了,但我仍然自己再翻译一遍是为了加深理解。这个翻译版本和别的不同在于很多地方采用了意译,加入了一些我自己的主观理解。 5 | 6 | 如果你在阅读过程中发现任何错误,欢迎提 issue 。 7 | 8 | ##安装 gitbook 9 | 10 | brew update 11 | brew install npm 12 | npm install gitbook -g 13 | 14 | ##生成 PDF 15 | 16 | gitbook pdf . ./rust_book.pdf 17 | 18 | ##生成 ePub 19 | 20 | gitbook epub . ./rust_book.epub 21 | 22 | ##生成 MOBI 23 | 24 | gitbook mobi . ./rust_book.mobi -------------------------------------------------------------------------------- /learn_rust/dining.md: -------------------------------------------------------------------------------- 1 | 哲学家就餐问题 2 | == 3 | 4 | 我们来尝试用 Rust 来解决经典的并发问题:哲学家就餐。这个问题最早由 Dijkstra 在 1965 年提出,不过这里我们参考的是 [Tony Hoare 1985]()的版本。 5 | 6 | 古时候有一个很有钱的慈善家为了五个杰出的哲学家捐资设立一所大学,用来专门供养他们进行思考和研究。五位哲学家有各自的办公室但是公用一个餐厅,餐厅里有张五个座位的圆桌,每个座位的左手边都有一把叉子。桌子中间是一大碗意大利面条, 7 | 8 | 通过哲学家就餐能形象地描述并发中会遇到的几个问题。并发问题是一类比较复杂的问题,常规的解决方法可能存在死锁。比如: 9 | 10 | 1.一个哲学家拿起左边的叉子开始吃 11 | 2.其余哲学家然后拿起右边的叉子 12 | 3.所有人开始吃 13 | 4.吃完所有人放下叉子 14 | 15 | 如果真这么做的话,就会发现: 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /syntax_semantics/generic.md: -------------------------------------------------------------------------------- 1 | 模板 2 | === 3 | 4 | 有时候我们想要自己的函数最大程度的复用,能适用于多种数据类型。还记得我们的 OptionalInt 类型吗? 5 | 6 | enum OptionalInt { 7 | Value(i32), 8 | Missing, 9 | } 10 | 11 | 如果我们还要一种 OptionalFloat64, 12 | 13 | enum OptionalFloat64 { 14 | Valuef64(f64), 15 | Missingf64, 16 | } 17 | 18 | 这么写很啰嗦。我们可以用 Rust 的模板: 19 | 20 | enum Option { 21 | Some(T), 22 | None, 23 | } 24 | 25 | 之前你可能见过这个 ,这是一个模板参数,在你使用 Option 的时候他会把 Option 内的 所有 T 替换成你指定的 T. -------------------------------------------------------------------------------- /effective_rust/releasechannel.md: -------------------------------------------------------------------------------- 1 | # 发布频道 2 | 3 | 每次新版本的 Rust 发布的时候都按三个频道(release channel)同时发布。理解这些频道可以帮助你选择使用适合于自己项目的 Rust 版本。 4 | 5 | #概括 6 | 7 | Rust 在三个频道同时发布新版本: 8 | 9 | * Nightly 10 | * Beta 11 | * Stable 12 | 13 | nightly 版本顾名思义就是每天创建一个;然后以六周为周期,这个周期内的最后一个 nightly 版本提升为 beta 版本,beta 版本只会接受 bugfix 不会增加新特性了;再下一个六周里 beta 版本就会被提升为 Stable 版本了,版本号 1.x 14 | 15 | # 选择版本 16 | 如果没有特殊的理由的话你应该选择 stable 版本。稳定版本就是为大多数用户准备的。 17 | 18 | 如果你想尝试或者需要一些新特性,那就选择 nightly 版本,不过这个版本不够稳定,你的代码可能没法顺利的从当前 nightly 版本迁移到下个 nightly 版本。 19 | 20 | # 通过 CI 来协助 Rust 开发 21 | 22 | 那么 Beta 版本是用来干嘛的呢?我们鼓励稳定版本的用户在编译完他们的项目之后也用 Beta 版本测试一下, -------------------------------------------------------------------------------- /syntax_semantics/aliase.md: -------------------------------------------------------------------------------- 1 | #类型别名 2 | 3 | 你可以用 `type` 关键字声明一个类型的别名: 4 | 5 | type Name = String; 6 | 7 | 然后使用这个别名: 8 | 9 | let x: Name = "Hello".to_string(); 10 | 11 | 因为这只是个别名,事实上是同一个类型,所以在比较类型的时候,得到的结果是一致的: 12 | 13 | type Num = i32; 14 | 15 | let x: i32 = 5; 16 | let y: Num = 5; 17 | 18 | if x == y { 19 | // ... 20 | } 21 | 22 | 我们经常把类型别名和模板放一起使用: 23 | 24 | use std::result; 25 | 26 | enum ConcreteError { 27 | Foo, 28 | Bar, 29 | } 30 | 31 | type Result = result::Result; 32 | 33 | 这里我们定制一个 Result 类型,用 ConcreteError 取代了 E。这个技巧在标准库里应用广泛。 -------------------------------------------------------------------------------- /syntax_semantics/unsafe.md: -------------------------------------------------------------------------------- 1 | 危险代码和底层代码 2 | === 3 | 4 | Rust 为底层 CPU 和操作系统提供了一层安全的抽象,但是我们有时候确实需要深入到底层做一些操作。底层操作是强大和危险的,这一章讲述Rust 的这部分。 5 | 6 | Rust 预留了一个后门叫做 `unsafe{}` 使得程序员能够躲开编译器的检查来做一些操作,比如: 7 | 8 | 解引用裸指针 9 | 通过外部接口调用函数 10 | 按字节强制类型转换 11 | 内联汇编 12 | 13 | ##指针 14 | 15 | ###引用 16 | 17 | Rust 最亮的特性就是内存安全。这是通过所有权系统来保证的,编译能保证每一个引用都是有效的,不会指向已被释放的内存。 18 | 19 | 关于引用的一些限制提供了安全保障但同时又会制约我们的使用。& 和 C 指针机制不一样不能用作外部函数接口内的指针。另外,可变引用和不可变引用都有别名和冻结的保证,为了安全。 20 | 21 | ###裸指针 22 | 23 | Rust 提供了另外两种指针(裸指针),记作 `*const T` 和 `*mut T` 分别对应于 C 的 `const *T` 和 `T*`。 24 | 他们最常用作 FFI 中和外部 C 库交互。 25 | 26 | 裸指针是没有Rust 中其他类型指针的保障的,例如: 27 | 28 | 并不保证它指向正确的内存也不保证不为空 29 | -------------------------------------------------------------------------------- /syntax_semantics/attribute.md: -------------------------------------------------------------------------------- 1 | 属性 2 | 3 | 我们可以给声明添加属性标记,例如: 4 | 5 | #[test] 6 | 7 | 或者 8 | 9 | #![test] 10 | 11 | 这两种添加属性的方式的唯一区别在于:#[test] 作用于后面的声明;#![test] 作用于它被包裹的那个声明(这里是 mod 声明)。这两者起到的作用是一样的,就是改变了声明的含义,比如: 12 | 13 | #[test] 14 | fn check() { 15 | assert_eq!(2, 1 + 1); 16 | } 17 | 18 | 这里 check 函数被标记上了 test 属性,当你运行 tests 的时候这个方法就会被调用。正常编译的时候会忽略这个函数,他就成了一个测试函数。当然还有其他风格的属性,例如包含限定词的属性: 19 | 20 | #[inline(always)] 21 | fn super_fast_fn() { 22 | 23 | 包含键值的属性: 24 | 25 | #[cfg(target_os = "macos")] 26 | mod macos_only { 27 | 28 | 不同的属性有着不同的用途,你可以参考这个[手册](https://doc.rust-lang.org/nightly/reference.html#attributes)。目前你还不能自定义属性,只能使用编译器替你设定好的。 -------------------------------------------------------------------------------- /syntax_semantics/match.md: -------------------------------------------------------------------------------- 1 | Match(模式匹配) 2 | === 3 | 4 | 很多时候简单的 if/else 是不够用的,因为我们需要处理的情况超过两种。而且 else 会让情况变得异常复杂,有什么好的解决方案呢? 5 | 6 | Rust 有一个关键字 `match`,让你能够处理复杂情况,请看: 7 | 8 | let x = 5; 9 | match x { 10 | 1 => println!("one"), 11 | 2 => println!("two"), 12 | 3 => println!("three"), 13 | 4 => println!("four"), 14 | 5 => println!("five"), 15 | _ => println!("something else"), 16 | } 17 | 18 | match 后面跟一个表达式,然后根据表达式的值来建立分支,每个分支的形式都是 `val => expression`. 值匹配,分值就会被执行。 19 | 20 | 使用 match 的最大好处在于编译器会帮你检查是否覆盖了所有的情况,如果我把上面最后一个分支去掉,编译器报错: 21 | 22 | error: non-exhaustive patterns: `_` not covered 23 | 24 | '_' 的意思是“剩余所有情况”,和前面形成互补,覆盖了所有可能的情况。 -------------------------------------------------------------------------------- /introduction.md: -------------------------------------------------------------------------------- 1 | 这本书旨在指导你学习 Rust 编程语言。Rust 是一门系统级编程语言,重点关注三个方面:安全、速度、并发。 2 | 3 | 《Rust 编程语言》分为七个部分。第一部分就是这篇介绍,接下来是: 4 | 5 | * [准备](getting_started/README.md) 配置好 Rust 开发环境 6 | * [先睹为快](learn_rust/README.md) 通过几个例子体验 Rust 是如何犀利地解决一些问题的 7 | * [Effective Rust](effective_rust/README.md) 如何写出高效的 Rust 代码 8 | * [语法和语义](syntax_sematic/README.md) 讲述 Rust 的每个细节 9 | * [Nightly Rust](nightly_rust/README.md) 最新的特性 10 | * [术语表](glossary/glossary.md) 专有名词 11 | * [学术研究](academic_research/README.md) 12 | 13 | 在读完这个介绍之后,如果你想即刻动手做个项目那么就跳到[先睹为快](learn_rust/README.md)章节;如果你想一点点了解语言的细节就跳到[语法和语义](syntax_sematic/README.md)章节。 14 | 15 | #贡献 16 | 17 | 这本书的源文件在 github.com/rust-lang/rust/tree/master/src/doc/trpl 18 | 19 | #Rust 简介 20 | -------------------------------------------------------------------------------- /syntax_semantics/comment.md: -------------------------------------------------------------------------------- 1 | Rust 有两种注释:行注释和文档注释。 2 | 3 | // 所有在 '//' 符号后面的都是行注释 4 | let x = 5; 5 | // 如果你的注释很长,你需要分行然后在每行开始添加行注释符, 6 | // 在行注释符和注释内容直接加上一个空格会让注释看上去更清晰 7 | 8 | 另外一种注释就是文档注释。用 /// 表示,并且支持 Markdown 语法: 9 | 10 | /// `hello` is a function that prints a greeting that is personalized based on 11 | /// the name given. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `name` - The name of the person you'd like to greet. 16 | /// 17 | /// # Example 18 | /// 19 | /// ```rust 20 | /// let name = "Steve"; 21 | /// hello(name); // prints "Hello, Steve!" 22 | /// ``` 23 | fn hello(name: &str) { 24 | println!("Hello, {}!", name); 25 | } 26 | 27 | 你可以用 rustdoc 工具为你添加了文档注释的代码生成 html 格式的文档。 28 | 29 | 写文档注释的时候,添加参数、返回值段落并且提供例子代码对阅读和维护你代码的人来说是非常非常有用的。 -------------------------------------------------------------------------------- /getting_started/install.md: -------------------------------------------------------------------------------- 1 | 安装 Rust 2 | === 3 | 4 | 使用 Rust 的第一步当然是安装了!有多种选择来安装 Rust,其中最简单的是使用 `rustup` 脚本。如果你用的是 Linux 或者 Mac 系统只需要在命令行下输入以下命令: 5 | 6 | $ curl -L https://static.rust-lang.org/rustup.sh | sudo sh 7 | 8 | 如果你担心使用 `curl | sudo sh` 的安全性,请阅读我们下面的声明。然后使用两步安装来检查我们的脚本: 9 | 10 | $ curl -L https://static.rust-lang.org/rustup.sh -O 11 | $ sudo sh rustup.sh 12 | 13 | 如果你用的是 Windows 请下载 32 位或者 64 位 安装包。 14 | 15 | 如果你想卸载 Rust 执行: 16 | 17 | $ curl -s https://static.rust-lang.org/rustup.sh | sudo sh -s -- --uninstall 18 | 19 | Windows 再次运行安装包,里面提供了卸载选项。 20 | 21 | 当你想要更新 Rust 版本的时候也只要执行和安装时候相同的脚本就行了。 22 | 23 | 目前官方支持的平台包括: 24 | 25 | * Windows (7, 8, Server 2008 R2) 26 | 27 | * Linux (2.6.18 or later, various distributions), x86 and x86-64 28 | 29 | * OSX 10.7 (Lion) 或更高版本, x86 and x86-64 30 | 31 | 安装完之后你可以查看安装版本: 32 | 33 | $rustc --version 34 | 35 | -------------------------------------------------------------------------------- /intermediate/collections.md: -------------------------------------------------------------------------------- 1 | 容器类型 2 | === 3 | 4 | Rust 的标准库高效地实现了大多数常用的数据结构。使用标准库里的实现可以避免一些数据格式转换有利于程序之间的通信。 5 | 6 | 我们平时用的最多的可能就是 Vec 和 HashMap。 7 | 8 | Rust 的容器类型可以被归为四类: 9 | 10 | * 线性:Vec, VecDeque, LinkedList, BitVec 11 | * Map: HashMap, BTreeMap, VecMap 12 | * 集合:HashSet, BTreeSet, BitSet 13 | * 混合:BinaryHeap 14 | 15 | ##我到底选择哪个容器? 16 | 17 | ###使用 Vec 的条件 18 | * 你想要把对象收集起来然后传递到别的地方,且不关心对象具体是什么类型 19 | * 你想要按一定顺序保存一序列元素,然后会在最后添加元素 20 | * 你需要一个 stack 数据结构 21 | * 你要一个大小可变的数组 22 | * 你需要一个堆上的数组 23 | 24 | ###使用 VecDeque 的条件 25 | * 你需要一个高效的能在两端插入元素的 vec 26 | * 你需要一个队列 27 | * 你需要一个双端队列 28 | 29 | ###使用 LinkedList 的条件 30 | 31 | ###使用 HashMap 的条件 32 | ###使用 BTreeMap 的条件 33 | ###使用 VecMap 的条件 34 | ###使用 Set 的条件 35 | ###使用 BitVec 的条件 36 | * 你要存放一些布尔类型,但是个数不一定 37 | * 你要一个存放 bit 的 Vector 38 | ###使用 BitSet 的条件 39 | 40 | * 你想要一个 BitVec, 但是还要有 Set 属性 41 | ###使用 BinaryHeap 的条件 42 | 43 | ##性能 44 | 45 | -------------------------------------------------------------------------------- /effective_rust/borrowasref.md: -------------------------------------------------------------------------------- 1 | Borrow 和 AsRef 2 | === 3 | 4 | Borrow 和 AsRef 这两个 trait 相似又有区别。 5 | 6 | #Borrow 7 | 8 | 在你设计数据结构的时候,如果希望数据既可以被持有又可以被借贷就要用到 Borrow 这个 trait. 例如 HashMap 有个 [get](https://doc.rust-lang.org/nightly/std/collections/struct.HashMap.html#method.get) 方法用到了 Borrow: 9 | 10 | fn get(&self, k: &Q) -> Option<&V> 11 | where K: Borrow, 12 | Q: Hash + Eq 13 | 14 | 大多数时候用 `&T` 来表示持有或者借贷类型就够了,但当我们要同时表示多种借贷类型的时候,Borrow 就非常有用了。例如你要设计一个方法能同时处理 &T 和 &mut T 类型: 15 | 16 | use std::borrow::Borrow; 17 | use std::fmt::Display; 18 | 19 | fn foo + Display>(a: T) { 20 | println!("a is borrowed: {}", a); 21 | } 22 | 23 | let mut i = 5; 24 | 25 | foo(&i); 26 | foo(&mut i); 27 | 28 | 结果会打印打次 "a is borrowed: 5" 29 | 30 | #AsRef 31 | 32 | AsRef 用来将值类型转换成引用类型, 类似 C# 装箱(Boxing): 33 | 34 | let s = "Hello".to_string(); 35 | 36 | fn foo>(s: T) { 37 | let slice = s.as_ref(); 38 | } 39 | 40 | #Borrow vs. AsRef 41 | 42 | 那在什么情况下应该用这个而不是另一个呢? 43 | 44 | -------------------------------------------------------------------------------- /syntax_semantics/drop.md: -------------------------------------------------------------------------------- 1 | Drop 2 | === 3 | 4 | 我们已经讨论过 traits 了,接下来我们来看标准库提供的一种特殊的 trait:`Drop`。Drop trait 使得你能够在离开作用域的地方插入一些代码,比如: 5 | 6 | struct HasDrop; 7 | 8 | impl Drop for HasDrop { 9 | fn drop(&mut self) { 10 | println!("Dropping!"); 11 | } 12 | } 13 | 14 | fn main() { 15 | let x = HasDrop; 16 | 17 | // do stuff 18 | 19 | } // x goes out of scope here 20 | 21 | 当 x 离开作用域后, `Drop` 就会被调用,Drop 有一个 `drop()` 方法,它接收一个可变引用 `self` 22 | 23 | struct Firework { 24 | strength: i32, 25 | } 26 | 27 | impl Drop for Firework { 28 | fn drop(&mut self) { 29 | println!("BOOM times {}!!!", self.strength); 30 | } 31 | } 32 | 33 | fn main() { 34 | let firecracker = Firework { strength: 1 }; 35 | let tnt = Firework { strength: 100 }; 36 | } 37 | 38 | 运行输出: 39 | 40 | BOOM times 100!!! 41 | BOOM times 1!!! 42 | 43 | Drop 主要用来清理和 struct 绑定的资源。比如,Arc 类型是一种引用计数类型,当 Drop 被调用的时候,引用计数会减一,如果引用计数变为零,值就会被销毁。 -------------------------------------------------------------------------------- /learn_rust/ffi.md: -------------------------------------------------------------------------------- 1 | 没有复杂的运行时(runtime)使得 Rust 程序变得非常轻量,可以方便的嵌入到别的语言中。 2 | 3 | 实际项目中往往同时用到好几种语言,因为不同的程序语言有各自的优缺点,掌握多种语言利用他们的优点犀利地解决各类问题是非常重要的。 4 | 5 | 大多数语言的运行时提供了强大的功能,开发效率非常高,但是运行效率却不高;这时候我们就想能不能在保留开发效率的同时也能获得运行时效率呢?我们可以利用各语言提供的 C 接口解决这类问题,用 C 编写一些关键代码然后再通过接口调用。这种接口叫做 FFI:"foreign function interface" 6 | 7 | Rust 支持两种形式的 FFI:从其他语言调用 Rust;在 Rust 里调用其他语言。 8 | 9 | 我们另外有一个完整的章节讲述 FFI,这一章就 Ruby、Python、JavaScript 为例讲一下如何在这些语言里调用 Rust。 10 | 11 | #Rust 库 12 | 13 | 14 | 15 | #Python 16 | 在 Python 里调用 Rust 就更简单了: 17 | 18 | from ctypes import cdll 19 | 20 | lib = cdll.LoadLibrary("target/release/libembed.so") 21 | 22 | lib.process() 23 | 24 | print("done!") 25 | 26 | 从 ctypes 里导入 cdll 模块后通过 LoadLibrary 方法加载 .so 库,然后就可以调用到 process() 方法了,在我的机子上耗时 0.017 秒。很快! 27 | 28 | #Node.js 29 | 30 | 为了在 Node.js 中调用 Rust 我们先要安装一个库: 31 | 32 | $npm install ffi 33 | 34 | 安装后就可以调用: 35 | 36 | var ffi = require('ffi'); 37 | 38 | var lib = ffi.Library('target/release/libembed', { 39 | 'process': ['void', []] 40 | }); 41 | 42 | lib.process(); 43 | 44 | console.log("done!"); 45 | 46 | #总结 47 | 48 | 把 Rust 嵌入到别的语言里非常简单。我们还可以利用 FFI 做更多,具体参考 FFI 章节。 -------------------------------------------------------------------------------- /effective_rust/ffi.md: -------------------------------------------------------------------------------- 1 | 外部函数接口(Foreign Function Interface) 2 | === 3 | 4 | ##介绍 5 | 6 | 这篇指南会以 [snappy](https://github.com/google/snappy) 压缩/解压 库为例介绍如何编写外部函数的绑定。Rust 目前还不能直接调用 C++ 库,但 snappy 有一个 C 接口(参考 [snappy-c.h](https://github.com/google/snappy/blob/master/snappy-c.h))。 7 | 8 | 下面是一个调用外部函数的例子,如果安装了 snappy 就能编译通过: 9 | 10 | extern crate libc; 11 | use libc::size_t; 12 | 13 | #[link(name = "snappy")] 14 | extern { 15 | fn snappy_max_compressed_length(source_length: size_t) -> size_t; 16 | } 17 | 18 | fn main() { 19 | let x = unsafe { snappy_max_compressed_length(100) }; 20 | println!("max compressed length of a 100 byte buffer: {}", x); 21 | } 22 | 23 | extern 块是外部库的函数记号列表。`#[link...]` 属性用来指示链接器链接 snappy 库来对上函数记号。 24 | 25 | 外部函数默认是危险的,所以调用他们需要置于 unsafe{} 内。C 库经常暴露出非线程安全的接口, 26 | 27 | ##创建安全的接口 28 | 29 | 30 | 31 | ##外部调用约定 32 | 33 | ##和外部代码交互 34 | 35 | ##空指针优化 36 | 37 | ##从 C 调用 Rust 38 | 39 | 有时候你可能需要编写能够从 C 调用的 Rust 函数,这很容易实现,只要添加一些东西: 40 | 41 | #[no_mangle] 42 | pub extern fn hello_rust() -> *const u8 { 43 | "Hello, world!\0".as_ptr() 44 | } 45 | 46 | extern 告诉编译器这个函数需要遵守C调用规范。no_mangle 属性关闭 Rust 的 name mangling,让函数更容易链接。 -------------------------------------------------------------------------------- /syntax_semantics/constatic.md: -------------------------------------------------------------------------------- 1 | #`const` 和 static 2 | Rust 里可以用 const 关键字定义常量: 3 | 4 | const N: i32 = 5; 5 | 6 | 和 let 绑定不同的是,这里必须显示指定 const 的类型,`: i32` 不能省略 7 | 8 | 常量在程序的整个生命周期中都存在。更重要的是编译器并不把常量放在固定的地方,而是以内联的方式把他们嵌入到每个使用到的地方。因此虽然你引用同一个常量,但他们的地址可能并不一致。 9 | 10 | 11 | #static 12 | 13 | Rust 通过 static 来定义全局变量,和 const 不一样的是 static 有个全局唯一的固定地址。 14 | 15 | static N: i32 = 5; 16 | 17 | 同样这里你也必须显示声明 static 的类型。 18 | 19 | static 变量也存在于程序的整个声明周期,所以任何常量的引用都有一个 `'static` 声明周期 20 | 21 | static NAME: &'static str = "Steve"; 22 | 23 | #可变性 24 | 25 | 你可以通过 mut 把 static 变量设置为可修改: 26 | 27 | static mut N: i32 = 5; 28 | 29 | 既然是全局变量那么就会出现一个线程在读取这个变量的时候另一个线程却在修改它,这种不一致可能导致严重后果,所以必须在 unsafe 里面操作: 30 | 31 | unsafe { 32 | N += 1; 33 | 34 | println!("N: {}", N); 35 | } 36 | 37 | 另外,任何 static 类型都必须 `Sync` 38 | 39 | #初始化 40 | 41 | const 和 static 都必须预先指定一个值,而且必须用常量表达式。使用函数或者在运行时指定都不行。 42 | 43 | #到底应该用哪个 44 | 45 | 如果两个都可以那么用 const.很少时候你需要给常量分配一个固定地址,and using a const allows for optimizations like constant propagation not only in your crate but downstream crates. 46 | 47 | const 可以理解成 C 的 `#define`: 他会导致代码变大但不会有运行时损耗。“在 C 里我们应该用 #define 还是 static?” 和 “在 Rust 里我应该用 const 还是 static?”是一个问题 -------------------------------------------------------------------------------- /learn_rust/guessgame.md: -------------------------------------------------------------------------------- 1 | 我们选择实现一个猜谜游戏作为我们第一个工程,通过完成这个游戏来了解 Rust 如何解决一些基本的编程问题。首先解释一下这个游戏:程序生成一个0 到 100 的随机数,然后让我们输入一个猜测;程序会告诉你,你的猜测大了还是小了。猜中了他会发出祝贺。听起来不错吧? 2 | 3 | ##搭建项目 4 | 5 | 我们先搭建一个项目。切换到你的工作目录。还记得我们是怎么创建 hello world 工程文件结构以及 Cargo.toml 文件的吗?Cargo 为我们提供了一个命令: 6 | 7 | $ cd ~/projects 8 | $ cargo new guessing_game --bin 9 | $ cd guessing_game 10 | 11 | 我们把项目名称 `guessing_game` 传给 `cargo new` 然后再加上 `--bin` 参数,这个参数表示我们要创建的是一个可执行程序而不是库。 12 | 13 | 然后根目录下会有 Cargo.toml 文件: 14 | 15 | [package] 16 | 17 | name = "guessing_game" 18 | version = "0.1.0" 19 | authors = ["Your Name "] 20 | 21 | 程序主文件 src/main.rs: 22 | 23 | fn main() { 24 | println!("Hello, world!"); 25 | } 26 | 27 | 然后我们可以用 cargo build 命令来编译工程,或者用 cargo run 来编译并且执行编译好的程序。 28 | 29 | #处理玩家的猜测 30 | 31 | 我们首先要让玩家能够输入猜测的数字,这就要用到标准输入输出库 std::io 32 | 33 | use std::io; // 引用标准库 34 | 35 | fn main() { 36 | println!("Guess the number!"); 37 | 38 | println!("Please input your guess."); 39 | 40 | let mut guess = String::new(); // 创建一个 String 类型存放玩家输入的字符串 41 | 42 | io::stdin().read_line(&mut guess) // 从标准输入读取一行 43 | .ok() 44 | .expect("Failed to read line"); 45 | 46 | println!("You guessed: {}", guess); 47 | } 48 | -------------------------------------------------------------------------------- /syntax_semantics/cast.md: -------------------------------------------------------------------------------- 1 | #类型转换 2 | 3 | 出于安全的考虑, Rust 提供了两种类型转换。一种是使用 as 关键字; 一种使用 transmute, 他可对任意类型进行转换,非常非常危险! 4 | 5 | #as 6 | 7 | 可以用 as 做一些基本的转换: 8 | 9 | let x: i32 = 5; 10 | 11 | let y = x as i64; 12 | 13 | 他只会对特定类型进行转换,当我们: 14 | 15 | let a = [0u8, 0u8, 0u8, 0u8]; 16 | 17 | let b = a as u32; // four eights makes 32 18 | 19 | 想把四字节转化成一个无符号整型的时候会报错: 20 | 21 | error: non-scalar cast: `[u8; 4]` as `u32` 22 | let b = a as u32; // four eights makes 32 23 | ^~~~~~~~ 24 | 25 | 想要完成这种转换要用到 transmute 26 | 27 | #transmute 28 | 29 | transmute 函数是由编译器 intrinsic 提供,他提供的操作既简单又可怕。他可以让 Rust 设定某个值为一个类型,尽管实际上他是另一个类型。使用它意味着忽略类型检测系统,一切由程序员来控制。 30 | 31 | 我们可以用 transmute 来完成之前的操作,把四个 u8 类型换换成 u32 类型: 32 | 33 | use std::mem; 34 | 35 | unsafe { 36 | let a = [0u8, 0u8, 0u8, 0u8]; 37 | 38 | let b = mem::transmute::<[u8; 4], u32>(a); 39 | } 40 | 41 | 我们必须把这个操作放在 unsafe 里面。 42 | 43 | transmute 几乎不做任何检查,只看两个类型的大小是否一致,如果一致就进行转换。当你使用 transmute 转换不同大小的类型时: 44 | 45 | use std::mem; 46 | 47 | unsafe { 48 | let a = [0u8, 0u8, 0u8, 0u8]; 49 | 50 | let b = mem::transmute::<[u8; 4], u64>(a); 51 | } 52 | 53 | 编译报错: 54 | 55 | error: transmute called on types with different sizes: [u8; 4] (32 bits) to u64 56 | (64 bits) -------------------------------------------------------------------------------- /syntax_semantics/operator.md: -------------------------------------------------------------------------------- 1 | Rust 支持有限的几个操作符重载。当你要重载某个操作符的时候只要实现特定的 trait 就可以了。 2 | 3 | 例如,可以通过实现 `Add` 这个 `trait` 来重载 `+` 操作符: 4 | 5 | use std::ops::Add; 6 | 7 | #[derive(Debug)] 8 | struct Point { 9 | x: i32, 10 | y: i32, 11 | } 12 | 13 | impl Add for Point { 14 | type Output = Point; 15 | 16 | fn add(self, other: Point) -> Point { 17 | Point { x: self.x + other.x, y: self.y + other.y } 18 | } 19 | } 20 | 21 | fn main() { 22 | let p1 = Point { x: 1, y: 0 }; 23 | let p2 = Point { x: 2, y: 3 }; 24 | 25 | let p3 = p1 + p2; 26 | 27 | println!("{:?}", p3); 28 | } 29 | 30 | 这里我们为 Point 类型实现了 Add 然后就能用 + 符号表示 Point 类型的加法了。 31 | 32 | 你可以在 std::ops 模块里找到所有可以重载的操作符。重载操作符的 trait 有着统一的范式,以 Add 为例: 33 | 34 | pub trait Add { 35 | type Output; 36 | 37 | fn add(self, rhs: RHS) -> Self::Output; 38 | } 39 | 这儿有三种类型。 40 | 在 `let z = x + y` 这样的表达式中 x 就是 Self 类型,y 就是 RHS,z 就是 Self::Output 类型。 41 | 42 | 如果给 Point 实现如下的 Add trait: 43 | 44 | impl Add for Point { 45 | type Output = f64; 46 | 47 | fn add(self, rhs: i32) -> f64 { 48 | // add an i32 to a Point and get an f64 49 | } 50 | } 51 | 52 | 就可以: 53 | 54 | let p: Point = // ... 55 | let x: f64 = p + 2i32; -------------------------------------------------------------------------------- /effective_rust/iterator.md: -------------------------------------------------------------------------------- 1 | 迭代器 2 | === 3 | 4 | 迭代器在 Rust 里应用非常广泛,常见的循环就是基于迭代器: 5 | 6 | for x in range(0i, 10i) { 7 | println!("{}", x); 8 | } 9 | 10 | range 函数返回一个迭代器。迭代器就是一种不停的调用 .next() 方法,得到一串连续对象的函数。比如: 11 | 12 | let mut range = range(0i, 10i); 13 | 14 | loop { 15 | match range.next() { 16 | Some(x) => { 17 | println!("{}",x); 18 | }, 19 | None => { break } 20 | } 21 | } 22 | 23 | 我们用 range 的返回值构造了一个可变绑定,这就是我们的迭代器。然后创建一个 loop, 内置 match.match 是用作 range.next() 结果匹配的。next 返回的是 Option 类型, 24 | 25 | 迭代器并不仅仅用于 for 循环。你可以实现 Iterator trait 来自定义自己的迭代器。这里暂且不表。 26 | 27 | range 还是语言的一个基础元素。当你需要对一个 vector 进行迭代时,你可能会这么写: 28 | 29 | let nums = vec![1i, 2i, 3i]; 30 | 31 | for i in range(0u, nums.len()) { 32 | println!("{}", nums[i]); 33 | } 34 | 35 | 这种写法比不上使用迭代器的写法,.iter() 方法会依次返回 vector 中每个元素的引用: 36 | 37 | let nums = vec![1i, 2i, 3i]; 38 | 39 | for num in nums.iter() { 40 | println!("{}", num); 41 | } 42 | 43 | 选择这种写法有两个原因。首先更直接地表达我们的意图。我们直接迭代 vector 内容而不是先迭代 vector 的索引再迭代 vector 内容。再者,这种写法效率更高:第一种用了 nums[i],这会引入越界检查 bounds checking.第二种用的是迭代器返回的引用,就不需要 bounds checking。这是迭代器常见的使用场景,不需要越界检查,我们的代码依然安全。 44 | 45 | 这儿有个关于 println! 的细节,num 其实是 &int 类型,println! 为我们做了解引用。也可以这么写: 46 | 47 | let nums = vec![1i, 2i, 3i]; 48 | 49 | for num in nums.iter() { 50 | println!("{}", *num); 51 | } 52 | 53 | 这就显式的解引用了 num。为什么 iter() 返回一个引用呢?避免拷贝! -------------------------------------------------------------------------------- /syntax_semantics/mutability.md: -------------------------------------------------------------------------------- 1 | #可变性 2 | 3 | 可变性就是表示一个绑定能不能被修改的属性。Rust 语言和其他语言不通在于所有绑定默认是不可修改的: 4 | 5 | let x = 5; 6 | x = 6; // 报错 7 | 8 | 我们要用 mut 关键字明确告诉编译器这个绑定是可以修改的之后才能修改它。 9 | 10 | 如果绑定本身是一个指针你也可以通过给它加上 mut 来修改它指向的对象: 11 | 12 | let mut x = 5; 13 | let y = &mut x; 14 | *y = 5; // OK 15 | 16 | 这里 y 本身是不能修改的,你不能将 y 重新绑定到另一个指针;但是你能通过它来修改它所指向的值。 17 | 18 | 另外重要的是 mut 也是[模式]()的一部分,你能够: 19 | 20 | let (mut x, y) = (5, 6); 21 | 22 | fn foo(mut x: i32) { 23 | 24 | } 25 | 26 | #内部可变性 vs. 外部可变性 27 | 28 | 29 | 30 | #字段级(Field-level)可变性 31 | 只有借贷(`&mut`)和绑定(`let mut`)有可变性。你也就无法给 struct 成员指定可变性: 32 | 33 | struct Point { 34 | mut x: f32, // 错误! 35 | mut y: i32 // 错误! 36 | } 37 | 38 | 你只能设置 Point 类型的绑定的可变性: 39 | 40 | struct Point { 41 | x: f32, 42 | y: f32 43 | } 44 | // p 可修改 45 | let mut p = Point{x: 1.0, y: 1.0}; 46 | p = Point{x: 2.0, y: 2.0}; 47 | // 成员也可修改 48 | p.x = 3.0; 49 | 50 | 如果我们确实要字段级别的可变性怎么办呢? 51 | 52 | 使用 [Cell](https://doc.rust-lang.org/nightly/std/cell/struct.Cell.html)! 53 | 54 | use std::cell::Cell; 55 | 56 | struct Point { 57 | x: i32, 58 | y: Cell, 59 | } 60 | 61 | let point = Point { x: 5, y: Cell::new(6) }; 62 | 63 | point.y.set(7); 64 | 65 | println!("y: {:?}", point.y); 66 | 67 | 我们成功修改了 y 的值,打印出结果 `y: Cell { value: 7 }.` -------------------------------------------------------------------------------- /syntax_semantics/module.md: -------------------------------------------------------------------------------- 1 | Crate 和 模块 2 | === 3 | 4 | 当项目变得越来越大,我们就需要将其模块化。 Rust 有一个模块系统。 5 | 6 | ##定义模块 7 | 8 | 我们用 mod 关键字定义我们的模块。我们的 src/lib.rs 写成: 9 | 10 | // in src/lib.rs 11 | 12 | mod english { 13 | mod greetings { 14 | 15 | } 16 | 17 | mod farewells { 18 | 19 | } 20 | } 21 | 22 | mod japanese { 23 | mod greetings { 24 | 25 | } 26 | 27 | mod farewells { 28 | 29 | } 30 | } 31 | 32 | ##多文件 Crate 33 | 34 | 如果把 crate 里的东西全放在一个文件,会显得拥挤。把他们分开放在不同的文件里更便于管理。我们有两种做法: 35 | 36 | 一、用 37 | 38 | mod english; 39 | 40 | 取代 41 | 42 | mod english { 43 | 44 | } 45 | 46 | 这样 Rust 就会去找一个叫 english.rs 或者 english/mod.rs 的文件。这样我们可以把上面的例子分解成 7 个文件,两个文件夹: 47 | 48 | $ tree . 49 | . 50 | ├── Cargo.lock 51 | ├── Cargo.toml 52 | ├── src 53 | │ ├── english 54 | │ │ ├── farewells.rs 55 | │ │ ├── greetings.rs 56 | │ │ └── mod.rs 57 | │ ├── japanese 58 | │ │ ├── farewells.rs 59 | │ │ ├── greetings.rs 60 | │ │ └── mod.rs 61 | │ └── lib.rs 62 | └── target 63 | ├── deps 64 | ├── libphrases-a7448e02a0468eaa.rlib 65 | └── native 66 | 67 | `src/lib.rs` 是我们的*根 crate* : 68 | 69 | mod english; 70 | mod japanese; 71 | 72 | 这两句告诉 Rust 去找 叫做 `src/english.rs` `src/japanese.rs` 或者 `src/english/mod.rs` `src/japanese/mod.rs` 的文件。这里我们有*子 crate*,所以是第二种。`src/english/mod.rs` `src/japanese/mod.rs` 是一样的: 73 | 74 | mod greetings; 75 | mod farewells; -------------------------------------------------------------------------------- /basic/input.md: -------------------------------------------------------------------------------- 1 | 标准输入 2 | === 3 | 4 | 获取键盘输入很简单,我们来看一下简单的将键盘的输入打印出来是怎么写的: 5 | 6 | fn main() { 7 | println!("Type something!"); 8 | 9 | let input = std::io::stdin().read_line().ok().expect("Failed to read line"); 10 | 11 | println!("{}", input); 12 | } 13 | 14 | 我们一句一句的看,首先: 15 | 16 | std::io::stdin(); 17 | 18 | 这是从 std::io 模块里调用 stdin() 函数。std 里面的东西都是由 Rust 标准库提供的。后面我们会降到模块系统。 19 | 20 | 每次调用都要输入前面的路径很麻烦,和 C++ 一样我们可以用 use 将函数导入进来: 21 | 22 | use std::io::stdin; 23 | stdin(); 24 | 25 | 但我们基本上不会只用某个模块里的一个函数,所以我们常常是导入一个模块: 26 | 27 | use std::io; 28 | io::stdin(); 29 | 30 | 用这种方法把例子重写一下: 31 | 32 | use std::io; 33 | fn main() { 34 | println!("Type something!"); 35 | let input = io::stdin().read_line().ok().expect("Failed to read line"); 36 | println!("{}", input); 37 | } 38 | 39 | 好了,我们接着看下一句: 40 | 41 | read_line() 42 | 43 | 在 stdin() 返回的结果上调用这个函数将会得到输入的一整行内容。很简洁。 44 | 45 | .ok().expect("failed to read line"); 46 | 47 | 还记得下面这个吗: 48 | 49 | enum OptionalInt { 50 | Value(i32), 51 | Missing, 52 | } 53 | 54 | fn main() { 55 | let x = OptionalInt::Value(5); 56 | let y = OptionalInt::Missing; 57 | 58 | match x { 59 | OptionalInt::Value(n) => println!("x is {}", n), 60 | OptionalInt::Missing => println!("x is missing!"), 61 | } 62 | 63 | match y { 64 | OptionalInt::Value(n) => println!("y is {}", n), 65 | OptionalInt::Missing => println!("y is missing!"), 66 | } 67 | } -------------------------------------------------------------------------------- /syntax_semantics/method.md: -------------------------------------------------------------------------------- 1 | 方法(Method) 2 | === 3 | 4 | 函数有时候用起来会不太方便,比如当我们对一个数据施加多个函数的时候: 5 | 6 | baz(bar(foo(x))); 7 | 8 | 我们的习惯是从左往右阅读:baz,bar,foo ;这和函数被调用的顺序是相反的。如果阅读的顺序和函数被调用的顺序一致岂不是更好?比如: 9 | 10 | x.foo().bar().baz(); 11 | 12 | 没错!Rust 是支持这种写法的,我们把这种调用方式叫做*方法调用*(method call)。可以通过 `impl` 关键字实现: 13 | 14 | struct Circle { 15 | x: f64, 16 | y: f64, 17 | radius: f64, 18 | } 19 | 20 | impl Circle { 21 | fn area(&self) -> f64 { 22 | std::f64::consts::PI * (self.radius * self.radius) 23 | } 24 | } 25 | 26 | fn main() { 27 | let c = Circle { x: 0.0, y: 0.0, radius: 2.0 }; 28 | println!("{}", c.area()); 29 | } 30 | 31 | 运行,打印得到 12.566371 32 | 33 | ##实例方法 34 | 35 | 我们定义了一个叫 Circle 的数据结构来表示圆,然后用 `impl` 为这个数据结构添加了一个叫 `area` 的方法用来计算圆的面积。方法如果有一个叫做 `self` 的特殊参数,这里的是 `&self`,另外还有 `self` 和 `&mut self` 可选,我们称之为*实例方法*(instance method)。如果数据结构是栈上的就用 `self`,如果我们想以引用的方式关联数据结构就用 `&self`,如果想以可变引用的方式关联数据结构就用 `&mut self`。`&self` 是最常用的。 36 | 37 | ##静态方法 38 | 39 | 另外还有一种不包含 self 参数的方法,我们叫做静态方法,也很常见: 40 | 41 | struct Circle { 42 | x: f64, 43 | y: f64, 44 | radius: f64, 45 | } 46 | 47 | impl Circle { 48 | fn new(x: f64, y: f64, radius: f64) -> Circle { 49 | Circle { 50 | x: x, 51 | y: y, 52 | radius: radius, 53 | } 54 | } 55 | } 56 | 57 | fn main() { 58 | let c = Circle::new(0.0, 0.0, 2.0); 59 | } 60 | 61 | 这个静态方法创建了一个 Circle 实例。需要注意的是这里调用使用的 `Struct::method` 写法,要和 `ref.method()` 写法区分开来。 62 | 63 | -------------------------------------------------------------------------------- /syntax_semantics/string.md: -------------------------------------------------------------------------------- 1 | Strings 2 | === 3 | 4 | 对任何一个程序员来说字符串都是掌握一门语言的必经之路。鉴于 Rust 语言的侧重,它的字符串处理系统有别于其他语言。任何时候只要有大小可变的数据结构,事情就会变得棘手,字符串恰好是大小可变的数据结构。 5 | 6 | 接下来我们看一些细节。 string 其实是被编码成 UTF-8 字节的 Unicode 标量序列。所有的string 一定是能被编码成 UTF-8 序列。另外,string 并不是以 null 结尾 并且可以包含 null 字节。 7 | 8 | Rust 有两种字符串类型: `&str` 和 `String`。 9 | 10 | 第一种 `&str`,叫做字符串切片(string slices)。字符串常量都是 `&str` 类型: 11 | 12 | let mystring = "Hello there."; // mystring: &str 13 | 14 | 这个 `Hello there.` 是静态分配的,直接编译到程序里,存在于整个程序运行周期内。 mystring 是静态分配的字符串的一个引用。字符串切片大小固定,无法修改。 15 | 16 | 17 | 另外一种是 `String`,是堆上的字符串。这个字符串大小可变,并且一定是 UTF-8 格式。 18 | 19 | let mut s = "Hello".to_string(); // mut s: String 20 | println!("{}", s); 21 | 22 | 你可以用 `as_slice()` 方法来获得 `String` 类型的一个切片: 23 | 24 | fn take_slice(slice: &str) { 25 | println!("Got: {}", slice); 26 | } 27 | 28 | fn main() { 29 | let s = "hello".to_string(); 30 | take_slice(s.as_slice()); 31 | } 32 | 33 | 比较 String 和字符串常量优先选择 `as_slice()`: 34 | 35 | fn compare(string: String) { 36 | if string.as_slice() == "Hello" { 37 | println!("yes"); 38 | } 39 | } 40 | 41 | 而不是 `to_string()`: 42 | 43 | fn compare(string: String) { 44 | if string == "Hello".to_string() { 45 | println!("yes"); 46 | } 47 | } 48 | 49 | 把 `String` 切成 `&str` 是高效的,反之把 `&str` 类型转换成 `String` 会引发内存分配。除非你明确自己非得转换成 `String`。 50 | 51 | 以上就是 Rust 字符串的基础部分!如果你之前一直使用的脚本语言,Rust 的字符串可能要比他们的要复杂一点,但是涉及到底层细节的时候,这些复杂性很有必要。现在你只要记住 `String` 会在堆上分配内存并管理之,而 `&str` 是对另一个字符串的引用就可以了。 52 | -------------------------------------------------------------------------------- /syntax_semantics/deref.md: -------------------------------------------------------------------------------- 1 | `Deref` 约束 2 | === 3 | 4 | 标准库提供了一个特殊的 trait 叫做 `Deref`。主要用来重载解引用运算符 `*`: 5 | 6 | use std::ops::Deref; 7 | 8 | struct DerefExample { 9 | value: T, 10 | } 11 | 12 | impl Deref for DerefExample { 13 | type Target = T; 14 | 15 | fn deref(&self) -> &T { 16 | &self.value 17 | } 18 | } 19 | 20 | fn main() { 21 | let x = DerefExample { value: 'a' }; 22 | assert_eq!('a', *x); 23 | } 24 | 25 | 这在写自定义指针类型的时候很有用。Rust 有个语言特性叫做“解引用约束”。如果你的类型 `U` 实现了 `Deref`,那么使用 &U 的时候会被约束成 &T,例如: 26 | 27 | fn foo(s: &str) { 28 | // borrow a string for a second 29 | } 30 | 31 | // String 实现了 Deref 32 | let owned = "Hello".to_string(); 33 | 34 | // therefore, this works: 35 | foo(&owned); 36 | 37 | 这是唯一能让 Rust 替你自动进行类型转换的方式。 38 | 39 | 还有一个例子就是: 40 | 41 | fn foo(s: &[i32]) { 42 | // borrow a slice for a second 43 | } 44 | 45 | // Vec implements Deref 46 | let owned = vec![1, 2, 3]; 47 | 48 | foo(&owned); 49 | 50 | Vector 类型被约束成 slice 51 | 52 | #解引用和方法调用 53 | 54 | 在调用一个方法的时候也会用到 Deref,例如: 55 | 56 | struct Foo; 57 | 58 | impl Foo { 59 | fn foo(&self) { println!("Foo"); } 60 | } 61 | 62 | let f = Foo; 63 | 64 | f.foo(); 65 | 66 | foo 需要一个 &self 参数,但 f 不是一个引用。那这里还能通过 f 来调用 foo 呢?在 Rust 里下面的调用都是等价的: 67 | 68 | f.foo(); 69 | (&f).foo(); 70 | (&&f).foo(); 71 | (&&&&&&&&f).foo(); 72 | 73 | 不管你引用了多少次,编译器会自动给加上对应次数的解引用。这里的解引用就用到了 Deref -------------------------------------------------------------------------------- /getting_started/cargo.md: -------------------------------------------------------------------------------- 1 | Cargo 2 | === 3 | 4 | Cargo 负责三件事:下载你代码中的依赖;编译这些依赖;最后编译你的代码。 5 | 6 | 如果你是通过官方的安装器来安装的 Rust ,那么同时你会安装好 Cargo。如果你用别的方法安装的请参考 Cargo 的 [README](https://github.com/rust-lang/cargo/blob/master/README.md) 来安装。 7 | 8 | 我们来把我们的 Hello World 程序转化成 Cargo 工程。 9 | 10 | 首先我们需要新建一个叫做 Cargo.toml 的文件,然后把源文件放到正确的目录下: 11 | 12 | $mkdir src 13 | $mv main.rs src/main.rs 14 | 15 | Cargo 默认的源文件路径为 src 目录,他和 README 和 license 都处于工程文件夹的最上层。Cargo 把我们的工程组织的非常整洁。 16 | 17 | 接下来编辑我们的配置文件: 18 | 19 | $editor Cargo.toml // 注意 C 必须是大写 20 | 21 | 写入: 22 | 23 | [package] 24 | name = "hello_world" 25 | version = "0.0.1" 26 | authors = [ "Your name " ] 27 | 28 | [[bin]] 29 | name = "hello_world" 30 | 31 | 这是 TOML 格式的文件,稍微解释一下: 32 | 33 | TOML aims to be a minimal configuration file format that's easy to read due to obvious semantics. TOML is designed to map unambiguously to a hash table. TOML should be easy to parse into data structures in a wide variety of languages. 34 | 35 | TOML 类似于 INI,但相比有些优点。 36 | 37 | 这个文件里定义了两个 table,一个叫做 package 一个叫做 bin,package 告诉 Cargo 你的工程的一些基本信息。bin 告诉 Cargo 我这是一个可执行程序工程不是一个库工程。 38 | 39 | 接下来你就可以编译了: 40 | 41 | $ cargo build 42 | Compiling hello_world v0.0.1 (file:///home/yourname/projects/hello_world) 43 | $ ./target/debug/hello_world 44 | Hello, world! 45 | 46 | 不管多大的项目你都可以用 cargo build 命令来完成编译,当你觉得程序足够稳定可以发布时,用 cargo build --release 可以用 47 | 48 | 你可能注意到 Cargo 还为我们创建了一个叫做 Cargo.lock 的文件: 49 | 50 | [root] 51 | name = "hello_world" 52 | version = "0.0.1" 53 | 54 | Cargo 使用这个文件来追踪项目里的依赖项。我们的例子里没有依赖项,所以这个 Cargo.lock 没什么用。你不要动这个文件,这是由 Cargo 自动处理的。 55 | 56 | 到这里我们成功地用 Cargo 构建了 hello_world,虽然程序比较简单,但我们学会了很有用的工具,以后的 Rust 项目里都会用到。 57 | -------------------------------------------------------------------------------- /syntax_semantics/if.md: -------------------------------------------------------------------------------- 1 | if 2 | === 3 | 4 | Rust 里的 if 并不复杂,更接近动态类型语言的用法。 5 | 6 | let x = 5; 7 | if x == 5 { 8 | println!("x is five!"); 9 | } 10 | 11 | 如果把 x 改成 5 以外的值,打印语句就不会执行。也就是说 if 后面的表达式结果为 true 就会被执行;如果是 false 则不会。 12 | 13 | 如果你想在 false 的时候做一些操作,那么就要用到 else: 14 | 15 | let x = 5; 16 | if x == 5 { 17 | println!("x is five!"); 18 | } else { 19 | println!("x is not five"); 20 | } 21 | 22 | 这是标准的写法,当然你也可以这么写: 23 | 24 | let x = 5; 25 | let y = if x == 5 { 26 | 10 27 | } else { 28 | 15 29 | }; // y: i32 30 | 31 | 甚至这么写: 32 | 33 | let x = 5; 34 | let y = if x == 5 { 10 } else { 15 }; // y: i32 35 | 36 | #表达式和声明 37 | 38 | Rust 是一门基于表达式的语言,除了两种的声明以外全都是表达式。 39 | 40 | 表达式和声明的区别在于表达式返回一个值而声明不会。在大多数语言里 if 只是个声明,因此像 `let x = if ...` 是没有意义的。但在 Rust 里 if 就是个表达式,他会返回一个值,我们也就可以用返回值来初始化绑定。 41 | 42 | 绑定是 rust 两种声明里的一种。 43 | 44 | 在一些语言里值绑定不仅可以写成声明还可以写成表达式,比如 Ruby : 45 | 46 | x = y = 5 47 | 48 | 而在 rust 里如果用 let 创建绑定那么这就不是一个表达式。这面的写法会导致编译错误: 49 | 50 | let x = (let y = 5); // expected identifier, found keyword `let` 51 | 52 | 编译器说这是个表达式而 let 只能用在声明里。 53 | 54 | 给绑定过的值赋值也是一个表达式。和 C 赋值之后得到一个新的值不一样的是在 Rust 里赋值操作的返回值是 单元类型 `()`, 这我们后面会说到。 55 | 56 | 第二种声明就是表达式声明。作用是把任意的表达式转化成声明。Rust 的语法是希望声明和声明连在一起,表达式之间要用 `;` 隔开。几乎每一句结尾都要跟个分号。 57 | 58 | 为什么说是“几乎”呢?比如: 59 | 60 | let x = 5; 61 | let y: i32 = if x == 5 { 10 } else { 15 }; 62 | 63 | 注意这里我们给 y 指定了类型 i32. 64 | 65 | 如果你写成这样: 66 | 67 | let x = 5; 68 | let y: i32 = if x == 5 { 10; } else { 15; }; 69 | 70 | 是不会编译通过的,报错: 71 | 72 | error: mismatched types: expected `i32`, found `()` (expected i32, found ()) 73 | 74 | 这里我们需要的是一个 i32 ,结果返回的是 (),我们称这个类型为单元,是 rust 的一个特殊类型。i32 类型的值是不能为 () 的。只有 () 类型的值才能是 () 。分号会忽略所有表达式的值然后返回 () 从而把表达式转化成声明。 75 | 76 | 还有一个地方你不需要分号,下一章《函数》里我们在介绍。 -------------------------------------------------------------------------------- /syntax_semantics/function.md: -------------------------------------------------------------------------------- 1 | 函数 2 | === 3 | 4 | 其实你已经见过一个函数了, main 函数: 5 | 6 | fn main() { 7 | 8 | } 9 | 10 | 这是最简单的函数声明,fn 表示这是一个函数,后面跟着函数的名称,如果没有参数的话,() 内为空,{} 表示函数体,再比如一个叫做 foo 的函数: 11 | 12 | fn foo() { 13 | 14 | } 15 | 16 | 如果有参数的话,比如打印一个数字: 17 | 18 | fn print_number(x: i32) { 19 | println!("x is: {}", x); 20 | } 21 | 22 | 看一下完整版本: 23 | 24 | fn main() { 25 | print_number(5); 26 | } 27 | 28 | fn print_number(x: i32) { 29 | println!("x is: {}", x); 30 | } 31 | 32 | 参数的写法和 let 类似,名称在前,类型在后,中间用 : 符号连接。 33 | 34 | 再写一个把两个数字相加的函数: 35 | 36 | fn main() { 37 | print_sum(5, 6); 38 | } 39 | 40 | fn print_sum(x: i32, y: i32) { 41 | println!("sum is: {}", x + y); 42 | } 43 | 44 | 声明和调用的时候不同参数之间都是用逗号隔开。 45 | 46 | 和 let 有所不同的是,你必须指定参数的类型。不指定没法编译通过: 47 | 48 | fn print_sum(x, y) { 49 | println!("sum is: {}", x + y); 50 | } 51 | 52 | 报错: 53 | 54 | hello.rs:5:18: 5:19 expected one of `!`, `:`, or `@`, found `)` 55 | hello.rs:5 fn print_number(x, y) { 56 | 57 | 理论上如果不指定参数类型,也能在调用的时候推断出来。某些语言就是这么干的,比如 haskell。不过这些语言也提倡在函数声明的时候就指定参数类型。还有一些与语言在函数体内也需要指定参数类型。Rust 设计者认为只在声明时候强制需要指定参数类型是一个很好地折中。 58 | 59 | 我们再看一下带返回值的函数怎么写: 60 | 61 | fn add_one(x: i32) -> i32 { 62 | x + 1 63 | } 64 | 65 | Rust 函数只能有一个返回值,在 `->` 这个箭头符号后面声明。 66 | 67 | 你可能发现这里面少了一个分号,如果加上一个分号: 68 | 69 | fn add_one(x: i32) -> i32 { 70 | x + 1 71 | } 72 | 73 | 会报错: 74 | 75 | error: not all control paths return a value 76 | fn add_one(x: i32) -> i32 { 77 | x + 1; 78 | } 79 | 80 | help: consider removing this semicolon: 81 | x + 1; 82 | ^ 83 | 84 | 还记得我们之前关于 `;` 和 `()` 的讨论吗,如果加上了这个分号我们的函数就变成了返回 `()`。这不是我们原意,所以编译器会建议我们去掉分号。 85 | 86 | 这很像我们的 if 声明: {} 块里面的计算结果就是返回值。Ruby 也使用了类似的语法,在系统编程语言里确实比较少见。人们开始的时候会认为这种语法会导致 BUG,但不要忘记 Rust 有非常健壮的类型系统,另外 () 也是一种特殊的类型,我还没见过因为遗漏或者多加了分号而导致的 BUG。 87 | 88 | 如果我们在函数运行途中就要返回呢?Rust 是提供了 return 关键字的: 89 | 90 | fn foo(x: i32) -> i32 { 91 | if x < 5 { return x; } 92 | x + 1 93 | } -------------------------------------------------------------------------------- /syntax_semantics/loop.md: -------------------------------------------------------------------------------- 1 | 循环 2 | === 3 | 4 | Rust 有两种方式表示循环:for 和 while. 5 | 6 | ##for 7 | 8 | `for` 用于固定次数的循环,Rust 的 `for` 循环和其他语言不太一样,不同于 C 语言的for循环: 9 | 10 | for (x=0; x < 10; x++) { 11 | printf("%d\n", x ); 12 | } 13 | 14 | Rust 的写法是这样子: 15 | 16 | for x in 0..10 { 17 | println!("{}",x ); // x: i32 18 | } 19 | 20 | 语法是: 21 | 22 | for var in expression { 23 | code 24 | } 25 | 26 | expression 是一个迭代器,我们会在进阶章节里面谈论这个。迭代器返回一个序列,序列里每个元素和 var 名称绑定然后参与一次循环的迭代,一次迭代结束迭代器会获取下一个,直到所有元素取完。 27 | 28 | 在我们的例子中 `0..10` 是一个返回开始和结束位置之间所有元素迭代器的表达式,这是一个前闭后开区间,所以我们的循环会打印 0 到 9 29 | 30 | Rust 故意不提供 c 风格的 for 循环,手动控制循环中的每个元素很复杂而且容易出错,哪怕你是有经验的 c 程序员。 31 | 32 | 在后面我们讨论迭代器的时候,会进一步讨论 for 循环。 33 | 34 | ##while 35 | 36 | 另一种表示循环的方式是用 `while`: 37 | 38 | let mut x = 5u32; // mut x: u32 39 | let mut done = false; // mut done: bool 40 | 41 | while !done { 42 | x += x - 3; 43 | println!("{}", x); 44 | if x % 5 == 0 { done = true; } 45 | } 46 | 47 | 当你不确定要循环多少次的时候,就应该选择 while 了。 48 | 当你要一个死循环的时候,可以写成: 49 | 50 | while true {} 51 | 52 | 不过 Rust 贴心的为我们提供 loop 关键字来表示死循环: 53 | 54 | loop {} 55 | 56 | Rust 的流程控制分析区别对待 `loop` 和 `while true`,`loop` 会明确告诉编译器我会一直循环下去,利于编译器更好的生成代码并提高安全性。所以当你需要使用死循环的时候应当选用 `loop` 57 | 58 | 59 | ##提前跳出迭代 60 | 61 | 让我们来看一下之前提到的 `while` 循环: 62 | 63 | let mut x = 5u32; 64 | let mut done = false; 65 | while !done { 66 | x += x - 3; 67 | println!("{}",x); 68 | if x % 5 == 0 { done = true; } 69 | } 70 | 71 | 这里我们用一个 mut 布尔值 done 来记录我们什么时候应该跳出循环。Rust 有两个关键字辅助我们来修改迭代: break 和 continue 72 | 73 | 这儿我们可以用 break 来改进上面那个循环: 74 | 75 | let mut x = 5u32; 76 | 77 | loop { 78 | x += x - 3; 79 | println!("{}", x); 80 | if x % 5 == 0 { break; } 81 | } 82 | 83 | loop 创建了一个死循环,然后条件满足 break 出循环。 84 | 85 | continue 是不执行当前迭代的剩余部分直接跳到下次迭代: 86 | 87 | for x in 0..10 { 88 | if x % 2 == 0 { continue; } 89 | println!("{}", x); 90 | } 91 | 92 | 以上只会打印出偶数。 93 | 94 | continue 和 break 在两种循环方式里都可以使用。 -------------------------------------------------------------------------------- /basic/array.md: -------------------------------------------------------------------------------- 1 | 数组,Vector 和 切片 2 | === 3 | 4 | ##数组 5 | 6 | 和其他编程语言一样,Rust 有一系列表示线性数据的数据结构。其中最基础的就是数组,数组由一排连续存储的同类型元素组成且大小固定。数组默认是不可变(immutable)的。 7 | 8 | let a = [1, 2, 3]; // 不可改写里面的成员 9 | let mut m = [1, 2, 3]; // 可改写里面的成员 10 | 11 | 相比于其他语言,在 Rust 里把数组初始化成特定的值要简单得多: 12 | 13 | let a = [0; 20]; // 初始值和大小用分号隔开 14 | 15 | 在 Rust 里数组是一个 `[T; N]` 类型。T 表示元素类型,N 表示元素个数。 16 | 17 | 你可以用 `.len()` 方法来获取元素个数,用 `.iteor()` 方法来循环迭代数组里每个元素。举例,依次打印每个元素: 18 | 19 | let a = [1, 2, 3]; 20 | 21 | println!("a has {} elements", a.len()); 22 | for e in a.iter() { 23 | println!("{}", e); 24 | } 25 | 26 | 你可以用下标获取特定位置的元素: 27 | 28 | let names = ["Graydon", "Brian", "Niko"]; // names: [&str; 3] 29 | println!("The second name is: {}", names[1]); 30 | 31 | 和大多数其他编程语言一样,Rust 数组的下标也从 `0` 开始。因此第一个元素是 `names[0]` 第二个是 `names[1]`。例子中将打印 `Brian`。如果你的下标越界了,会返回一个错误:“array access is bounds-checked at run-time”。数组越界在其他编程语言里是很多问题的根源。 32 | 33 | ##Vector 34 | 35 | vector 是动态的或者说大小可变的数组,是一个标准库类型: `Vec`。vector 相对于数组好比 `String` 相对于 `&str`。你可以用 `vec!` 宏创建 vector。 36 | 37 | let v = vec![1, 2, 3]; // v: Vec 38 | 39 | 如果想把所有成员初始化一样的值: 40 | 41 | let v = vec![0; 20]; // 20 个成员全初始化为 0 42 | 43 | 你可以像数组一样获取 vector 的长度,迭代里面的元素,用下标去索引里面的元素。vector 还可以自动增长: 44 | 45 | let mut nums = vec![1, 2, 3]; // mut nums: Vec 46 | nums.push(4); 47 | println!("The length of nums is now {}", nums.len()); // Prints 4 48 | 49 | vector 还内置了非常多有用的方法。这个你可以查询[标准库手册](http://doc.rust-lang.org/std/)。 50 | 51 | ##切片 52 | 53 | 切片(slice)是数组的一个引用(或者叫视图 view)。切片用于安全地、高效地获取数组的一部分而不需要引入拷贝。例如,你只需要获取读入内存的文件的某一行,这时候就应该使用切片。切片无法直接创建,都是在现有数据上切出来的。切片有一个长度,可设置为可变或者不可变,很多方面和数组类似: 54 | 55 | let a = [0, 1, 2, 3, 4]; 56 | let middle = &a[1..4]; // 切出元素 1, 2, 3;切片是一个前开后闭区间 57 | 58 | for e in middle.iter() { 59 | println!("{}", e); // Prints 1, 2, 3 60 | } 61 | 62 | `Vector`, `String`, `&str` 都是可切的,因为他们都是基于数组的类型。切片是 `&[T]` 类型。 63 | 64 | 到这里 Rust 的基础部分就学完了。我们可以开始编写一个猜谜游戏了,不过还要知道怎么从键盘获取输入。因为如果不能通过键盘输入我们的猜测,这个游戏就玩不起来。 -------------------------------------------------------------------------------- /effective_rust/document.md: -------------------------------------------------------------------------------- 1 | 文档 2 | === 3 | 4 | 文档在软件工程中是非常重要的,有时候比代码本身还要重要。我认为文档工具是现代编程语言的标配,既然 Rust 声称自己是现代(modern)的编程语言当然也提供了文档工具。 5 | 6 | ##rustdoc 7 | 8 | Rust 的安装包里面包含了一个叫做 `rustdoc` 的工具,可以用它来生成文档。`cargo doc` 命令其实也是调用的 `rustdoc`。 9 | 10 | 文档来源有两种,一种是你的源代码注释,一种是单独的 markdown 文件。 11 | 12 | ##给你的代码写文档 13 | 14 | 按照一定的格式给 rust 代码写注释就可以方便地用 `rustdoc` 来自动生成文档: 15 | 16 | /// 创建一个 `Rc` 类型 17 | /// 18 | /// # Example 19 | /// 20 | /// ``` 21 | /// use std::rc::Rc; 22 | /// 23 | /// let five = Rc::new(5); 24 | /// ``` 25 | pub fn new(value: T) -> Rc { 26 | // ... 27 | } 28 | 29 | 注意,用于生成文档的注释应该是三个斜杠 `///` 开头,注释里面的内容是 markdown 格式的。 30 | 31 | 一开始写文档的时候经常会犯一些错误,比如: 32 | 33 | /// The `Option` type. See [the module level documentation](../) for more. 34 | enum Option { 35 | None, /// No value 36 | Some(T), /// Some value `T` 37 | } 38 | 39 | 这里把文档注释写在了一行代码的最后,调用 rustdoc 生成文档的时候会报错: 40 | 41 | hello.rs:4:1: 4:2 error: expected ident, found `}` 42 | hello.rs:4 } 43 | ^ 44 | 这是因为文档注释作用于注释后面 `///` 的语句,而 `/// Some value `T`` 之后没有语句了,所以报错。 45 | 46 | ##编写文档注释 47 | 48 | 我们再看前面这个例子: 49 | 50 | /// 创建一个 `Rc` 类型 51 | 52 | 文档注释的第一行应该是关于下面这段代码作用的一个概述。就一句话简单的概括就行。 53 | 概括性的描述之后,如果有必要详细的说明一下,就另起一段: 54 | 55 | /// 56 | /// 在这里描述一下各个参数的意义,具体的使用方法,注意事项等等 57 | /// ... ... 58 | /// 59 | 60 | 61 | 62 | ###特殊段落 63 | 64 | rustdoc 定义了一些特殊段落,它们以 # 符号开头 65 | 66 | /// # Examples 67 | 68 | 这个段落的你可以写上两三个例子来描述一下用法 69 | 70 | /// # Panics 71 | 72 | 不可恢复的错误在 Rust 里通常是由 panic 来表示的,如果的函数里可能会发生这样的错误,你可以在这个段落里说明一下。 73 | 74 | /// # Failtures 75 | 76 | 如果你的方法或者函数返回一个 Result,在这里描述一下什么情况下会返回 Err(E) 就会很有用。 77 | 78 | /// # Safety 79 | 80 | 如果你的代码是危险的(unsafe)你应该在这个段落里告诉使用者注意事项。 81 | 82 | ###在文档中标示代码 83 | 84 | 你可以用三点符号来标示注释中的代码: 85 | 86 | /// ``` 87 | /// println!("Hello, world"); 88 | /// ``` 89 | 90 | 如果要写 Rust 以外的代码,可以加一个符号: 91 | 92 | /// ```c 93 | /// printf("Hello, world\n"); 94 | /// ``` 95 | 96 | 它可以高亮任意的语言,如果是纯文本就用 `text`。 97 | 值得注意的是,这里你一定要选择正确的符号,如果你明明是 c++ 代码却选了 c 的符号,rustdoc 是会报错的。 98 | 99 | -------------------------------------------------------------------------------- /syntax_semantics/macro.md: -------------------------------------------------------------------------------- 1 | 宏 2 | === 3 | 4 | 到现在为止我们已经学习了非常多的抽象和复用代码的工具。 5 | 6 | ##定义一个宏 7 | 8 | 你已经用过 vec! 这个宏了,用它可以初始化若干个 vector 成员: 9 | 10 | let x: Vec = vec![1, 2, 3]; 11 | 12 | 这不是一个普通的函数,因为它可以传入任意个参数。但我们可以把它看做是下面代码的简写: 13 | 14 | let x: Vec = { 15 | let mut temp_vec = Vec::new(); 16 | temp_vec.push(1); 17 | temp_vec.push(2); 18 | temp_vec.push(3); 19 | temp_vec 20 | }; 21 | 22 | 我们可以用宏来表达这种简写: 23 | 24 | macro_rules! vec { 25 | ( $( $x:expr ),* ) => { 26 | { 27 | let mut temp_vec = Vec::new(); 28 | $( 29 | temp_vec.push($x); 30 | )* 31 | temp_vec 32 | } 33 | }; 34 | } 35 | 36 | 我们一句一句来看,首先 37 | 38 | macro_rules! vec { ... } 39 | 40 | 这是定义了一个叫做 `vec` 的宏,好似 `fn vec` 定义了一个叫做 `vec` 的方法。调用宏的时候我们要加上感叹号,一边和普通方法调用区分开:`vec!` 41 | 42 | #Matching 43 | 44 | 宏是由一组规则定义的,这些规则使用模式匹配来描述: 45 | 46 | ( $( $x:expr ),* ) => { ... }; 47 | 48 | 这像是模式匹配的一个条件分支。 49 | 50 | `$x:expr` 会匹配任意的 Rust 表达式,把语法树绑定到元变量 `$x`,expr 是一个 fragment specifier. 51 | 把匹配条件写在 $(...),* 里面表示匹配零个或者多个用逗号分开的表达式。 52 | 53 | 出了特殊的匹配符号,表达式里面的每个符号都要匹配。比如: 54 | 55 | macro_rules! foo { 56 | (x => $e:expr) => (println!("mode X: {}", $e)); 57 | (y => $e:expr) => (println!("mode Y: {}", $e)); 58 | } 59 | 60 | fn main() { 61 | foo!(y => 3); 62 | } 63 | 64 | 打印出 65 | 66 | mode Y: 3 67 | 68 | 如果是 69 | 70 | foo!(z => 3) 71 | 72 | 就会报错: 73 | 74 | error: no rules expected the token `z` 75 | 76 | #Expansion 77 | 78 | #Repetition 79 | 80 | #Hygiene 81 | 82 | C 语言的宏有个著名的问题: 83 | 84 | #define FIVE_TIMES(x) 5 * x 85 | 86 | int main() { 87 | printf("%d\n", FIVE_TIMES(2 + 3)); 88 | return 0; 89 | } 90 | 91 | 这里的宏被展开后得到的表达式是 `5 * 2 + 3` 使用 Rust 的宏的时候我们不需要担心这个问题: 92 | 93 | macro_rules! five_times { 94 | ($x:expr) => (5 * $x); 95 | } 96 | 97 | fn main() { 98 | assert_eq!(25, five_times!(2 + 3)); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /syntax_semantics/binding.md: -------------------------------------------------------------------------------- 1 | 值绑定 2 | === 3 | 4 | 你可能第一次听到`绑定`这种说法,在 Rust 里绑定需要用到关键字 `let` 5 | 6 | fn main() { 7 | let x = 5; 8 | } 9 | 10 | 一看,咦,不就是别的程序语言里的赋值嘛!恩,这里的 `let` 只是赋值,但它还可以有另一层含义:模式匹配(pattern matching),等号两边的会自动对应起来。rust 可以写出这样的语句: 11 | 12 | let (x, y) = (1, 2); 13 | 14 | 这个表达式执行之后 x 变成 1, y 变成 2。模式匹配是 Rust 一个非常强大的语法,这个我们后面会详细的讲。 15 | 16 | Rust 是一门静态类型语言,也就是说在赋值前要明确值的类型,上面那个例子可以编译通过是因为 rust 可以根据给出的值自动推导出类型(类似于 c++ 里的 auto),当然你也可以明确一下类型: 17 | 18 | let x: i32 = 5; 19 | 20 | 这里我们用了 i32 类型,这是有符号32位整型,Rust 还有其他的整型。都是以 `i`(有符号) 或者 `u`(无符号) 开头后面跟位数,例如 `i64`,`u32` 等等。 21 | 22 | 值绑定默认创建的是不可变(immutable)类型, 23 | 24 | let x = 10; 25 | x = 11; 26 | 27 | 这么写是没法编译通过的,报错: 28 | 29 | error: re-assignment of immutable variable `x` 30 | x = 10; 31 | ^~~~~~~ 32 | 33 | 如果需要一个可变类型的绑定,需要加上 mut 关键字 : 34 | 35 | let mut x = 10; 36 | x = 11; 37 | 38 | 关于绑定还有一个需要了解的是,在使用值绑定之前需要初始化: 39 | 40 | fn main() { 41 | let x: i32; 42 | 43 | println!("Hello world!"); 44 | } 45 | 46 | 这里我们只是声明了一个没有初始化的 x 绑定,没有使用它,编译器只是报了警: 47 | 48 | Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world) 49 | src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variable)] on by default 50 | src/main.rs:2 let x: i32; 51 | ^ 52 | 53 | 当我们使用这个没有初始化的绑定的时候,编译器就会报错了: 54 | 55 | fn main() { 56 | let x: i32; 57 | 58 | println!("The value of x is: {}", x); 59 | } 60 | 61 | $ cargo build 62 | Compiling hello_world v0.0.1 (file:///home/you/projects/hello_world) 63 | src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x` 64 | src/main.rs:4 println!("The value of x is: {}", x); 65 | ^ 66 | note: in expansion of format_args! 67 | :2:23: 2:77 note: expansion site 68 | :1:1: 3:2 note: in expansion of println! 69 | src/main.rs:4:5: 4:42 note: expansion site 70 | error: aborting due to previous error 71 | Could not compile `hello_world`. 72 | 73 | Rust 是不允许使用未经初始化的值的。另外我们在解释一下 `println!` 宏的用法。 74 | 75 | 第一个参数是一个字符串,里面还有一对大括号,这对大括号叫做 String Interpolation。它的作用相当于一个标记,就是告诉编译器我这有个位置你帮我填充一个值。这个要填充的值就是第二个参数,和第一个参数之间用逗号隔开。 76 | 77 | Rust 可以打印[多种值](https://doc.rust-lang.org/nightly/std/fmt/) 目前我们只是打印最简单的整型。 -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | Rust 编程语言 2 | === 3 | 4 | 你好!这本书旨在为你学习 [Rust](http://www.rust-lang.org/) 编程语言提供一些指导。Rust 是一门专注于安全、速度和并发的现代系统级编程语言。 5 | 6 | 《Rust 编程语言》分为八章,第一章就是这篇介绍,其余七章分别是: 7 | * [准备](getting_started/README.md) 8 | * [安装](getting_started/install.md) 9 | * [Hello world!](getting_started/helloworld.md) 10 | * [Hello Cargo](getting_started/cargo.md) 11 | * [先睹为快](learn_rust/README.md) 12 | * [猜谜游戏](learn_rust/guessgame.md) 13 | * [哲学家就餐](learn_rust/dining.md) 14 | * [在其他语言里调用 Rust](learn_rust/ffi.md) 15 | * [Effective Rust](effective_rust/README.md) 16 | * [堆和栈](effective_rust/stackheap.md) 17 | * [测试](effective_rust/test.md) 18 | * [条件编译](effective_rust/conditianalcompilation.md) 19 | * [文档](effective_rust/document.md) 20 | * [迭代器](effective_rust/iterator.md) 21 | * [并发](effective_rust/concurrency.md) 22 | * [错误处理](effective_rust/error.md) 23 | * [FFI](effective_rust/ffi.md) 24 | * [Borrow 和 AsRef](effective_rust/borrowasref.md) 25 | * [Release 频道](effective_rust/releasechannel.md) 26 | * [语法和语义](syntax_semantics/README.md) 27 | * [值绑定](syntax_semantics/binding.md) 28 | * [函数](syntax_semantics/function.md) 29 | * [基本类型](syntax_semantics/primtype.md) 30 | * [注释](syntax_semantics/comment.md) 31 | * [if](syntax_semantics/if.md) 32 | * [循环](syntax_semantics/loop.md) 33 | * [所有权](syntax_semantics/ownership.md) 34 | * [引用和借贷](syntax_semantics/refborrow.md) 35 | * [生命周期](syntax_semantics/lifetime.md) 36 | * [可变性](syntax_semantics/mutability.md) 37 | * [结构体](syntax_semantics/struct.md) 38 | * [枚举](syntax_semantics/enum.md) 39 | * [匹配](syntax_semantics/match.md) 40 | * [模式](syntax_semantics/pattern.md) 41 | * [方法](syntax_semantics/method.md) 42 | * [vector](syntax_semantics/vector.md) 43 | * [string](syntax_semantics/string.md) 44 | * [模板](syntax_semantics/generic.md) 45 | * [Trait](syntax_semantics/trait.md) 46 | * [Drop](syntax_semantics/drop.md) 47 | * [if let](syntax_semantics/iflet.md) 48 | * [Trait Object](syntax_semantics/traitobject.md) 49 | * [闭包](syntax_semantics/closure.md) 50 | * [Crate 和模块](syntax_semantics/module.md) 51 | * [const 和 static](syntax_semantics/constatic.md) 52 | * [属性](syntax_semantics/attribute.md) 53 | * [类型别称](syntax_semantics/aliase.md) 54 | * [强制转换](syntax_semantics/cast.md) 55 | * [关联类型](syntax_semantics/associated.md) 56 | * [变长类型](syntax_semantics/unsized.md) 57 | * [操作符及其重载](syntax_semantics/operator.md) 58 | * [Deref coercions](syntax_semantics/deref.md) 59 | * [宏](syntax_semantics/macro.md) 60 | * [裸指针](syntax_semantics/pointer.md) 61 | * [unsafe](syntax_semantics/unsafe.md) 62 | * [Nightly Rust](nightly_rust/README.md) 63 | * [编译器插件](nightly_rust/compilerplugin.md) 64 | * [术语表](glossary/glossary.md) 专有名词 65 | * [学术研究](academic_research/README.md) -------------------------------------------------------------------------------- /syntax_semantics/pattern.md: -------------------------------------------------------------------------------- 1 | 模式(Patterns) 2 | === 3 | 4 | 我们前面讲到的 `let` 和 `match` 其实已经用到了模式。这个现在我们看一下 `match` 的一些高级用法。 5 | 6 | 首先 `match` 可用于匹配字面量(literals),结合 `_` 可以方便地表达一些条件匹配: 7 | 8 | let x = 1; 9 | 10 | match x { 11 | 1 => println!("one"), 12 | 2 => println!("two"), 13 | 3 => println!("three"), 14 | _ => println!("anything"), 15 | } 16 | 17 | 你还可以用 `|` 同时匹配多个模式: 18 | 19 | let x = 1; 20 | 21 | match x { 22 | 1 | 2 => println!("one or two"), 23 | 3 => println!("three"), 24 | _ => println!("anything"), 25 | } 26 | 27 | 更强大的是,你可以用 `...` 匹配一个范围: 28 | 29 | let x = 1; 30 | 31 | match x { 32 | 1 ... 5 => println!("one through five"), 33 | _ => println!("anything"), 34 | } 35 | 36 | 范围匹配常用于 int 型和字符型。 37 | 38 | 如果你用 | 或者 ... 同时匹配多个值的话,可以使用 @ 来给他们绑定个名称: 39 | 40 | let x = 1; 41 | 42 | match x { 43 | e @ 1 ... 5 => println!("got a range element {}", e), 44 | _ => println!("anything"), 45 | } 46 | 47 | 在匹配一个枚举类型的时候,你可以用 .. 忽略掉枚举的值和类型: 48 | 49 | enum OptionalInt { 50 | Value(i32), 51 | Missing, 52 | } 53 | 54 | let x = OptionalInt::Value(5); 55 | 56 | match x { 57 | OptionalInt::Value(..) => println!("Got an int!"), 58 | OptionalInt::Missing => println!("No such luck."), 59 | } 60 | 61 | 你还可以用 if 来控制 match: 62 | 63 | enum OptionalInt { 64 | Value(i32), 65 | Missing, 66 | } 67 | 68 | let x = OptionalInt::Value(5); 69 | 70 | match x { 71 | OptionalInt::Value(i) if i > 5 => println!("Got an int bigger than five!"), 72 | OptionalInt::Value(..) => println!("Got an int!"), 73 | OptionalInt::Missing => println!("No such luck."), 74 | } 75 | 76 | ##指针和引用的匹配 77 | 78 | 当你 match 指针类型的时候,应该加上 & 符号: 79 | 80 | let x = &5; 81 | 82 | match x { 83 | &val => println!("Got a value: {}", val), 84 | } 85 | 86 | 如果需要对引用进行 match 应该使用 ref 关键字: 87 | 88 | let x = 5; 89 | 90 | match x { 91 | ref r => println!("Got a reference to {}", r), 92 | } 93 | 94 | 如果你还要这个引用是可变的,那么加上 mut: 95 | 96 | let mut x = 5; 97 | 98 | match x { 99 | ref mut mr => println!("Got a mutable reference to {}", mr), 100 | } 101 | 102 | ##匹配 struct 中的成员 103 | 104 | 甚至你还可以对 struct 里面的成员进行匹配: 105 | 106 | struct Point { 107 | x: i32, 108 | y: i32, 109 | } 110 | 111 | let origin = Point { x: 0, y: 0 }; 112 | 113 | match origin { 114 | Point { x: x, y: y } => println!("({},{})", x, y), 115 | } 116 | 117 | 如果我们只关心某一个成员的话可以用 `..` 符号忽略其余成员: 118 | 119 | struct Point { 120 | x: i32, 121 | y: i32, 122 | } 123 | 124 | let origin = Point { x: 0, y: 0 }; 125 | 126 | match origin { 127 | Point { x: x, .. } => println!("x is {}", x), 128 | } 129 | 130 | ##匹配数组和切片 131 | 132 | 如果你需要匹配切片或者数组的话,就要用 `&`: 133 | 134 | fn main() { 135 | let v = vec!["match_this", "1"]; 136 | 137 | match &v[..] { 138 | ["match_this", second] => println!("The second element is {}", second), 139 | _ => {}, 140 | } 141 | } 142 | 143 | 总之有着非常多匹配东西的方法,这些方法还可以相互组合相互匹配,看你怎么用了。 144 | 145 | match x { 146 | Foo { x: Some(ref name), y: None } => ... 147 | } 148 | 149 | 模式是非常强大的,好好利用! -------------------------------------------------------------------------------- /effective_rust/concurrency.md: -------------------------------------------------------------------------------- 1 | 并发 2 | === 3 | 4 | 并发和并行是计算机科学里非常重要的课题,现今在实际生产中也是一个热门话题。如今的计算机有着越来越多的核心,我们程序员却没能充分运用。 5 | 6 | Rust 在并发的时候也能够保证内存安全。 7 | 8 | 在开始讨论并发之前首先要明确 Rust 提供了最基本的元素,然后并发是在这些基本元素上实现的。也就是说,如果对 Rust 提供的并发不满意,你可以自己去实现。[mio](https://github.com/carllerche/mio) 就是一个例子。 9 | 10 | ##Send , Sync 11 | 12 | Rust 提供了两个 trait 来帮助代码产生并发 13 | 14 | ##Send 15 | 16 | 如果一个类型实现了 Send ,就是告诉编译器我这个类型里的一些东西的所有权能够安全的在线程之间传递。 17 | 18 | 比如我们用一个管道连通两个线程,通过管道把一些数据传到另一个线程,我们就要保证这些数据实现了 Send。 19 | 20 | 相反,如果我们封装了一个并不是线程安全的 FFI 库,我们就不实现 Send 从而让编译帮助我们保证数据只能在当前线程里传递。 21 | 22 | 23 | 24 | ##Sync 25 | 26 | 第二个 trait 叫做 Sync。当一个类型实现了 Sync 就等于告诉编译器这个类型跨线程使用是线程安全的。 27 | 28 | 以上两种 trait 借助于 Rust 的类型系统为你的并发代码提供了强有力的保障。为什么呢?我们先来看一下 Rust 里并发代码什么样。 29 | 30 | ##Threads 31 | 32 | Rust 的标准库里有个线程库,我们用这个来编写并行的代码,举例: 33 | 34 | use std::thread::Thread; 35 | 36 | fn main() { 37 | Thread::scoped(|| { 38 | println!("Hello from a thread!"); 39 | }); 40 | } 41 | 42 | 这里我们传了一个闭包给 Thread::scoped() ,然后闭包就会被调度到新线程里执行。之所以叫做 scoped ,是因为他会返回一个 join 监视: 43 | 44 | use std::thread::Thread; 45 | 46 | fn main() { 47 | let guard = Thread::scoped(|| { 48 | println!("Hello from a thread!"); 49 | }); 50 | 51 | // guard goes out of scope here 52 | } 53 | 54 | 在 guard 生命周期结束的时候,如果线程还没执行完,程序会被阻塞,直到执行完为止。如果这不是你想要的,你可以用 `Thread::spawn()`: 55 | 56 | use std::thread::Thread; 57 | use std::old_io::timer; 58 | use std::time::Duration; 59 | 60 | fn main() { 61 | Thread::spawn(|| { 62 | println!("Hello from a thread!"); 63 | }); 64 | 65 | timer::sleep(Duration::milliseconds(50)); 66 | } 67 | 68 | 或者使用 `.detach()`: 69 | 70 | use std::thread::Thread; 71 | use std::old_io::timer; 72 | use std::time::Duration; 73 | 74 | fn main() { 75 | let guard = Thread::scoped(|| { 76 | println!("Hello from a thread!"); 77 | }); 78 | 79 | guard.detach(); 80 | 81 | timer::sleep(Duration::milliseconds(50)); 82 | } 83 | 84 | 我们这用 sleep 是因为在 main 退出的时候会杀死所有正在执行的线程。 85 | 86 | scoped 有一个有意思的类型符号: 87 | 88 | fn scoped<'a, T, F>(self, f: F) -> JoinGuard<'a, T> 89 | where T: Send + 'a, 90 | F: FnOnce() -> T, 91 | F: Send + 'a 92 | 93 | 94 | ##Channels 95 | 96 | 我们可以用管道替代 sleep 来同步: 97 | 98 | use std::sync::{Arc, Mutex}; 99 | use std::thread::Thread; 100 | use std::sync::mpsc; 101 | 102 | fn main() { 103 | let data = Arc::new(Mutex::new(0u32)); 104 | 105 | let (tx, rx) = mpsc::channel(); 106 | 107 | for _ in (0..10) { 108 | let (data, tx) = (data.clone(), tx.clone()); 109 | 110 | Thread::spawn(move || { 111 | let mut data = data.lock().unwrap(); 112 | *data += 1; 113 | 114 | tx.send(()); 115 | }); 116 | } 117 | 118 | for _ in 0 .. 10 { 119 | rx.recv(); 120 | } 121 | } 122 | 123 | 我们用 mpsc::channel() 方法创建了一个管道,在一端发送 `()` 然后另一端等待接收。 124 | 125 | #Panics 126 | 127 | 当前线程执行过程中崩溃会返回一个 `panic!`,你可以利用这个把线程和其他代码隔离开来: 128 | 129 | use std::thread; 130 | 131 | let result = thread::spawn(move || { 132 | panic!("oops!"); 133 | }).join(); 134 | 135 | assert!(result.is_err()); 136 | 137 | -------------------------------------------------------------------------------- /effective_rust/test.md: -------------------------------------------------------------------------------- 1 | 测试 2 | === 3 | 4 | 大多数程序员都不重视测试,我们先聆听一下 Dijkstra 的教诲: 5 | 6 | Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence. 7 | 8 | Edsger W. Dijkstra, "The Humble Programmer" (1972) 9 | 10 | 通过测试能找出我们程序里的 bug,但无法保证我们的程序不出 bug。 11 | 12 | 这里我门就来学习在 Rust 里如何正确地测试我们的代码。 13 | 14 | ##test 属性 15 | 16 | 给函数加上一个 test 属性,他就成了一个最简单的测试。我还是先创建一个工程吧: 17 | 18 | $ cargo new adder 19 | $ cd adder 20 | 21 | 在创建新工程的时候 Cargo 会为自动为你在 src/lib.rs 里生成一个测试: 22 | 23 | #[test] 24 | fn it_works() { 25 | } 26 | 27 | `#[test]` 就表示这是一个测试函数。我通过 cargo 来运行这个测试: 28 | 29 | $ cargo test 30 | Compiling adder v0.0.1 (file:///home/you/projects/adder) 31 | Running target/adder-91b3e234d4ed382a 32 | 33 | running 1 test 34 | test it_works ... ok 35 | 36 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured 37 | 38 | Doc-tests adder 39 | 40 | running 0 tests 41 | 42 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured 43 | 44 | 认真的你会发现 cargo 执行了两个测试,一个是我们写的测试,另一个是文档测试(暂且不表)。"test it_works ... ok" 表明我们的测试通过了。 45 | 那为什么我们什么都没做的测试都通过了呢,在 rust 里只要不 panic 的测试都能算作通过。我们来试一下通不过的测试: 46 | 47 | #[test] 48 | fn it_works() { 49 | assert!(false); 50 | } 51 | 52 | assert! 是 Rust 内置的一个宏,他有一个布尔类型的参数,如果这个参数是 true,什么都不做,如果为 false 就会 panic! 程序。我们执行一下: 53 | 54 | $ cargo test 55 | Compiling adder v0.0.1 (file:///home/you/projects/adder) 56 | Running target/adder-91b3e234d4ed382a 57 | 58 | running 1 test 59 | test it_works ... FAILED 60 | 61 | failures: 62 | 63 | ---- it_works stdout ---- 64 | thread 'it_works' panicked at 'assertion failed: false', /home/steve/tmp/adder/src/lib.rs:3 65 | 66 | 67 | 68 | failures: 69 | it_works 70 | 71 | test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured 72 | 73 | thread '
' panicked at 'Some tests failed', /home/steve/src/rust/src/libtest/lib.rs:247 74 | 75 | 除了这些错误信息之外我们还得到一个操作系统的状态量: 76 | 77 | $ echo $? 78 | 101 79 | 80 | 当你把 cargo test 集成到别的工具里的时候或许有用。 81 | 82 | 我们还能给测试添加 `should_panic` 属性: 83 | 84 | #[test] 85 | #[should_panic] 86 | fn it_works() { 87 | assert!(false); 88 | } 89 | 90 | 这个属性的意思是说 这个方法是可以 panic 的,我们试一下: 91 | 92 | $ cargo test 93 | Compiling adder v0.0.1 (file:///home/you/projects/adder) 94 | Running target/adder-91b3e234d4ed382a 95 | 96 | running 1 test 97 | test it_works ... ok 98 | 99 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured 100 | 101 | Doc-tests adder 102 | 103 | running 0 tests 104 | 105 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured 106 | 107 | rust 还有一个 assert_eq! 的宏,用于比较两个参数是否相等: 108 | 109 | #[test] 110 | #[should_panic] 111 | fn it_works() { 112 | assert_eq!("Hello", "world"); 113 | } 114 | 115 | 同样因为 should_panic 的存在,测试也通过了: 116 | 117 | $ cargo test 118 | Compiling adder v0.0.1 (file:///home/you/projects/adder) 119 | Running target/adder-91b3e234d4ed382a 120 | 121 | running 1 test 122 | test it_works ... ok 123 | 124 | test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured 125 | 126 | Doc-tests adder 127 | 128 | running 0 tests 129 | 130 | test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured 131 | 132 | ##test 模块 133 | 134 | ##test 目录 135 | 136 | 标注了 test 属性的函数就是一个最简单的测试。 137 | 138 | ##文档测试 139 | 140 | ##性能测试 -------------------------------------------------------------------------------- /basic/compound.md: -------------------------------------------------------------------------------- 1 | 复合数据类型 2 | === 3 | 4 | 和其他语言一样 Rust 提供了非常多的内置类型。我们已经学习整型、字符串类型的一些简单用法,现在讨论复杂一点的数据表示方式。 5 | 6 | ##tuples 7 | 我们第一个讨论的复合数据类型叫做 tuple,tuple 是一个固定大小的有序列表: 8 | 9 | let x = (1, "hello"); 10 | 11 | 这儿构造了一个长度为二的 tuple,如果要把 tuple 的类型标注出来的话: 12 | 13 | let x: (i32, &str) = (1, "hello"); 14 | 15 | 相信你已经看出来了,tuple 类型就是这个 tuple 各个成员类型的列表。这有个 `&str` 类型你之前可能没有见过,这人先告诉你这叫做字符串切片(string slice),后面讲字符串高级部分的时候会讲到。 16 | 17 | 我们说tuple 是个有序列表就是说在比较两个 tuple 的时候,不但他们各个成员的类型和值要一样,连他们出现的顺序也必须一样,他们才会相等。 18 | 19 | let x = (1, 2, 3); 20 | let y = (2, 2, 4); 21 | 22 | if x == y { 23 | println!("yes"); 24 | } else { 25 | println!("no"); 26 | } 27 | 28 | 只要有一个成员不一样就不等。 29 | 30 | let x = (1, 2, 3); 31 | let y = (2, 1, 3); 32 | 33 | if x == y { 34 | println!("yes"); 35 | } else { 36 | println!("no"); 37 | } 38 | 39 | 成员的顺序不一样也不等。 40 | 41 | tuple 另外一个非常重要的功能,当我们的函数需要返回多个值时,我们可以返回一个 tuple。 42 | 43 | fn next_two(x: i32) -> (i32, i32) { (x + 1, x + 2) } 44 | 45 | fn main() { 46 | let (x, y) = next_two(5); 47 | println!("x, y = {}, {}", x, y); 48 | } 49 | 50 | #struct 51 | 52 | struct 是比 tuple 更常见更通用的类型,他们的区别在于 struct 有名字 tuple 没有: 53 | 54 | let black = (255,255,255); // tuple 55 | 56 | struct Color(i32, i32, i32); 57 | let black = Color(255,255,255); // struct 58 | 59 | 这里 black != origin 60 | 61 | ##成员初始化 62 | 63 | 为了避免每次绑定 struct 的时候都要对各个成员进行赋值来进行初始化,我们可以替 struct 实现 Default trait,然后通过调用 Default::default() 来进行初始化: 64 | 65 | use std::default::Default; 66 | 67 | struct Color { 68 | r: i32, 69 | g: i32, 70 | b: i32, 71 | a: i32, 72 | } 73 | 74 | impl Default for Color { 75 | fn default() -> Color { 76 | Color{r: 255, g: 255, b: 255, a: 255} 77 | } 78 | } 79 | 80 | fn main() { 81 | // 绑定一个默认值 82 | let black = Color::default(); 83 | println!("color:{},{},{},{}", color.r, color.g, color.b, color.a); 84 | // 把 r 设为 128,其余为默认 85 | let color = Color{r: 128, ..Default::default()}; 86 | println!("color:{},{},{},{}", color.r, color.g, color.b, color.a); 87 | } 88 | 89 | #tuple struct 90 | 91 | Rust 还有一种复合数据类型叫做 tuple struct, 它自己有名字但是成员没有名字: 92 | 93 | struct Color(i32, i32, i32); 94 | struct Point(i32, i32, i32); 95 | 96 | 这里要注意,不同的名字代表不同的类型,即使他们里面的数据都一样: 97 | 98 | let black = Color(0, 0, 0); 99 | let origin = Point(0, 0, 0); 100 | 101 | 绝大多数情况下你应该使用 struct 而不是 tuple struct。通常我们把 Color 和 Point 写作: 102 | 103 | struct Color { 104 | red: i32, 105 | blue: i32, 106 | green: i32, 107 | } 108 | 109 | struct Point { 110 | x: i32, 111 | y: i32, 112 | z: i32, 113 | } 114 | 115 | 用名称将成员区别开来有利于减少错误发生。 116 | 117 | 但是有一种 tuple struct 非常有用,就是只包含一个数据成员的 tuple struct: 118 | 119 | struct Inches(i32); 120 | 121 | let length = Inches(10); 122 | 123 | let Inches(integer_length) = length; 124 | println!("length is {} inches", integer_length); 125 | 126 | 这相当于给 i32 起了个别名,同时也创建一个新类型叫 Inches 127 | 128 | #enum 129 | 最后就是 Rust 里非常重要的枚举类型,enum 在标准库里被大量使用。和其他语言一样 enum 表示“从成员类型中选一种”。下面的例子定义了一个字符类型,它可能是数字或者别的(字母或者符号),但是不可能又是数字又是别的(字母或符号)。 130 | 131 | enum Character { 132 | Digit(i32), 133 | Other, 134 | } 135 | 136 | enum 里面的成员可以是任何类型,下面的类型都可以往 enum 里放: 137 | 138 | struct Empty; 139 | struct Color(i32, i32, i32); 140 | struct Length(i32); 141 | struct Status { Health: i32, Mana: i32, Attack: i32, Defense: i32 } 142 | struct HeightDatabase(Vec); -------------------------------------------------------------------------------- /syntax_semantics/closure.md: -------------------------------------------------------------------------------- 1 | 闭包 2 | === 3 | 4 | 我们写了很多函数,他们都有一个名字。Rust 也能创建匿名函数。Rust 的匿名函数叫做闭包(closure)。 5 | 6 | 创建一个 闭包: 7 | 8 | let add_one = |x: i32| x + 1; 9 | 10 | assert_eq!(2, add_one(1)); 11 | 12 | 我们使用 `|...|{...} ` 句法创建一个闭包,然后绑定一个名称。注意,我们使用绑定的名称加小括号来调用这个函数,和调用普通函数一样。 13 | 14 | 我们比较一下句法。很接近: 15 | 16 | let add_one = |x: i32| x + 1; 17 | fn add_one (x: i32) -> i32 { 1 + x } 18 | 19 | 你可能已经注意到,闭包可以推断出参数和返回值,因此没必要声明一个。这点和实名函数不一样。 20 | 21 | 22 | ##闭包和上下文 23 | 24 | 之所以叫做闭包是因为它创建了一个作用域: 25 | 26 | let num = 5; 27 | let plus_num = |x: i32| x + num; 28 | 29 | assert_eq!(10, plus_num(5)); 30 | 31 | 这里 plus_num 其实包含了后面闭包作用域里的 num 的一个绑定。当我们违反绑定规则就会报错,比如: 32 | 33 | let mut num = 5; 34 | let plus_num = |x: i32| x + num; 35 | 36 | let y = &mut num; 37 | 38 | 报错: 39 | 40 | error: cannot borrow `num` as mutable because it is also borrowed as immutable 41 | let y = &mut num; 42 | ^~~ 43 | note: previous borrow of `num` occurs here due to use in closure; the immutable 44 | borrow prevents subsequent moves or mutable borrows of `num` until the borrow 45 | ends 46 | let plus_num = |x| x + num; 47 | ^~~~~~~~~~~ 48 | note: previous borrow ends here 49 | fn main() { 50 | let mut num = 5; 51 | let plus_num = |x| x + num; 52 | 53 | let y = &mut num; 54 | } 55 | ^ 56 | 57 | 错误信息很明确的告诉我们不能再借用 num 了, 因为在当前上下文里它仍被 plus_num 所持有。明确闭包的上下文,可以解决这个问题: 58 | 59 | let mut num = 5; 60 | { 61 | let plus_num = |x: i32| x + num; 62 | 63 | } // plus_num goes out of scope, borrow of num ends 64 | 65 | let y = &mut num; 66 | 67 | 有时候 num 会被强制加入闭包的上下文中: 68 | 69 | let nums = vec![1, 2, 3]; 70 | 71 | let takes_nums = || nums; 72 | 73 | println!("{:?}", nums); 74 | 75 | 这里因为 Vector 类型是没法拷贝的,闭包拿走 nums 的所有权: 76 | 77 | note: `nums` moved into closure environment here because it has type 78 | `[closure(()) -> collections::vec::Vec]`, which is non-copyable 79 | let takes_nums = || nums; 80 | ^~~~~~~ 81 | 82 | 这和你把 Vector 作为参数传递给函数时候是一样。 83 | 84 | ##转移闭包(move closures) 85 | 86 | Rust 还有另外一种闭包,叫做转移闭包。转移闭包用 move 关键字标示。转移闭包和普通闭包不同之处在于,转移闭包总是拿走所有用到变量的 ownership。相比之下普通闭包只是在当前栈帧内创建了一个引用。转移闭包最大的用处是和 Rust 的并发结合(参见并发章节)。这里暂且不表,后面在线程章节里面再谈论。 87 | 88 | ##闭包作为参数 89 | 90 | 闭包最为参数传递给一谈函数很有用,举个例子: 91 | 92 | fn twice i32>(x: i32, f: F) -> i32 { 93 | f(x) + f(x) 94 | } 95 | 96 | fn main() { 97 | let square = |x: i32| { x * x }; 98 | 99 | twice(5, square); // evaluates to 50 100 | } 101 | 102 | 可能有点小复杂,我们分解一下,首先我们定义了一个返回 i32 值并带有两个参数的模板函数 twice, 特殊之处在于这个模板是一个函数模板 `F: Fn(i32) -> i32` 也就是说任意一个函数只要满足,传入的是一个 i32 然后返回一个 i32 就行。twice 的第二个参数使用了前面定义的模板,意思就是这个参数必须是函数,且这个函数参数是 i32 返回值是 i32。 103 | 104 | 很显然闭包在这起到把一个函数传递给另一个函数的作用,这是一个非常强大的功能。 105 | 106 | 接下来讲一个闭包值得注意的地方,当我们同时传入两个闭包给同一函数时候 107 | 108 | fn compose(x: i32, f: F, g: G) -> i32 109 | where F: Fn(i32) -> i32, G: Fn(i32) -> i32 { 110 | g(f(x)) 111 | } 112 | 113 | fn main() { 114 | compose(5, 115 | |n: i32| { n + 42 }, 116 | |n: i32| { n * 2 }); // evaluates to 94 117 | } 118 | 119 | 你可能会纳闷为什么这要用两个模板 `F` 和 `G` 他们的标识符都是一样的呀,都是 `Fn(i32) -> i32`(参入一个 i32 返回一个 i32 的函数)嘛。其实,标识符只限定了闭包的入口和出口,两个闭包入口和出口虽然一样但内部的操作可能完全不同,比如上面一个是 `n + 42` 一个是 `n * 2`。所以 Rust 内部规定任意一个闭包都是单独一个特殊类型,不但不同标识符的闭包类型不一样,同样标识符的闭包类型也不一样。 120 | 121 | 这里引入了一个 where,用 where 来指定模板参数类型。 122 | 123 | ##闭包的实现 124 | 125 | Rust 里的闭包其实是 trait 的语法糖,在阅读接下来的内容的时候请确保对 trait 有所了解。 126 | 理解闭包原理的关键在于意识到函数调用符号 () 是一个重载操作符。Rust 里使用 trait 系统来实现操作符重载,函数调用操作符也不例外 127 | 128 | pub trait Fn : FnMut { 129 | extern "rust-call" fn call(&self, args: Args) -> Self::Output; 130 | } 131 | 132 | pub trait FnMut : FnOnce { 133 | extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output; 134 | } 135 | 136 | pub trait FnOnce { 137 | type Output; 138 | 139 | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; 140 | } 141 | 142 | 开始学习闭包的时候可能会有点不习惯,一旦你开始使用并习惯使用的时候就会有一种爱不释手的感觉。在关键的地方闭包非常有用,比如后面会讲到的迭代器。 143 | -------------------------------------------------------------------------------- /syntax_semantics/traitobject.md: -------------------------------------------------------------------------------- 1 | Trait Objects 2 | === 3 | 4 | 当涉及到多态的时候,有一种判断到底应该调用哪个版本的机制,这种机制叫做调度(dispatch)。有两种调度,静态调度和动态调度。Rust 更倾向于使用静态调度,但是也支持动态调度;动态调度是通过 trait objects 来实现的。 5 | 6 | ##准备 7 | 8 | 为了后面讨论方便,我们先写一个 trait 的例子: 9 | 10 | trait Foo { 11 | fn method(&self) -> String; 12 | } 13 | 14 | 然后替 `u8` 和 `String` 类型实现这个 trait : 15 | 16 | impl Foo for u8 { 17 | fn method(&self) -> String { format!("u8: {}", *self) } 18 | } 19 | 20 | impl Foo for String { 21 | fn method(&self) -> String { format!("string: {}", *self) } 22 | } 23 | 24 | ##静态调度 25 | 26 | 我们可以通过 trait 绑定来实现静态调度: 27 | 28 | fn do_something(x: T) { 29 | x.method(); 30 | } 31 | 32 | fn main() { 33 | let x = 5u8; 34 | let y = "Hello".to_string(); 35 | 36 | do_something(x); 37 | do_something(y); 38 | } 39 | 40 | 这里 Rust 用了一种叫做 monomorphization 的方法来实现静态调度,就是生成了两个版本的 do_something ,然后在调用的地方替换成对应版本。也就是 Rust 生成了如下的代码: 41 | 42 | fn do_something_u8(x: u8) { 43 | x.method(); 44 | } 45 | 46 | fn do_something_string(x: String) { 47 | x.method(); 48 | } 49 | 50 | fn main() { 51 | let x = 5u8; 52 | let y = "Hello".to_string(); 53 | 54 | do_something_u8(x); 55 | do_something_string(y); 56 | } 57 | 58 | 这种实现的优势在于方法调用可以内联,效率更高;弊端在于,如果有非常多版本的方法,二进制代码的体积会迅速膨胀,因为编译器替每个版本的 trait 实现都生成了一个函数。 59 | 60 | 另外值得注意的是编译器并不是万能的,它的优化可能导致代码运行变慢。比如过多的使用内联函数会阻塞指令缓存(instruction cache),因此你应该小心使用`#[incline]` 和 `#[inline(always)]`。同样的原因,静态调度是有可能比动态调度慢的。 61 | 62 | ##动态调度 63 | 64 | Rust 通过 trait objects 来实现动态调度。像 `&Foo` ,`Box` 这样的 trait object 包含了一个实现了 Foo trait 的对象。这个对象的类型具体什么类型只有在运行时才知道。 65 | 66 | trait object 可以通过转化指针来获得,但这个指针指向的对象必须实现了相关的 trait。比如 `&x as &Foo`; 或者是强制转换,比如一个以 &Foo 为参数的函数,你调用时候传入 `&x` (注意!这里是引用,和前面的静态调度不一样), 编译器会把 &x 转换成 trait object。这些转换同样适用于可变类型的指针,例如把 `&mut T` 转换到 `&mut Foo` 也适用于 Box 类型,比如把 `Box` 转换到 `Box`。 67 | 68 | 这些转换的实际作用是消除编译器对指针类型的记录,trait object 因此也叫做“类型消除”。 69 | 70 | 回到上面的例子,我们尝试用把指针转换到 trait object 的方法来实现动态调度: 71 | 72 | fn do_something(x: &Foo) { 73 | x.method(); 74 | } 75 | 76 | fn main() { 77 | let x = 5u8; 78 | do_something(&x as &Foo); 79 | // 或者 80 | // do_something(&x); 81 | 82 | let y = "test".ToString(); 83 | do_something(&y as &Foo); 84 | // 或者 85 | // do_something(&y); 86 | } 87 | 88 | 以 trait object 为参数的函数只有一份,并不会像静态调度那样生成多个版本的函数。和静态调度相比避免了代码的膨胀,但是引入了虚函数调用的开销而且编译器也很难对这类函数进行内联等相关的优化。 89 | 90 | ##为什么用指针? 91 | 92 | 和其他语言不一样,Rust 并没有默认使用指针来表示一些类型。在 rust 里不同类型有着不同的长度,这个长度信息在参数传递和把他从栈上移到堆上是非常重要的。 93 | 94 | 但在这里,我们需要一个对象既能装下 `String` 又能装下 `u8`,不用指针是很难办到的,除非这个对象非常非常大,但是这么做很浪费。 95 | 96 | 把对象放在指针后面意味着我们不需要关心对象的大小了,只要关心指针本身的大小。 97 | 98 | ##[trait object 的表示](http://programmers.stackexchange.com/a/247313/79822) 99 | 100 | trait 的所有方法可以通过 trait object 里面一个 vtable 类型的指针来调用。(vtable 由编译器创建和管理) 101 | 102 | 我们从简单的 trait object 运行时表示开始,Rust 源代码的 `std::raw` 模块里包含了 trait object 的结构体: 103 | 104 | pub struct TraitObject { 105 | pub data: *mut (), 106 | pub vtable: *mut (), 107 | } 108 | 109 | 也就是说一个 trait object 包含了 data 指针和 vtable 指针。 110 | 111 | data 指针指向 trait object 里保存的数据, vtable 指针指向了实现了 Foo Trait 的类型的 vtable。 112 | 113 | vtable 是一个包含了若干个函数指针的结构体,这些函数指针指向该类型实现的 trait 方法。一个像 trait_object.method() 这样的方法调用会从 vtable 里获取一个对应的指针然后动态调用它: 114 | 115 | struct FooVtable { 116 | destructor: fn(*mut ()), 117 | size: usize, 118 | align: usize, 119 | method: fn(*const ()) -> String, 120 | } 121 | 122 | // u8: 123 | 124 | fn call_method_on_u8(x: *const ()) -> String { 125 | // the compiler guarantees that this function is only called 126 | // with `x` pointing to a u8 127 | let byte: &u8 = unsafe { &*(x as *const u8) }; 128 | 129 | byte.method() 130 | } 131 | 132 | static Foo_for_u8_vtable: FooVtable = FooVtable { 133 | destructor: /* compiler magic */, 134 | size: 1, 135 | align: 1, 136 | 137 | // cast to a function pointer 138 | method: call_method_on_u8 as fn(*const ()) -> String, 139 | }; 140 | 141 | 142 | // String: 143 | 144 | fn call_method_on_String(x: *const ()) -> String { 145 | // the compiler guarantees that this function is only called 146 | // with `x` pointing to a String 147 | let string: &String = unsafe { &*(x as *const String) }; 148 | 149 | string.method() 150 | } 151 | 152 | static Foo_for_String_vtable: FooVtable = FooVtable { 153 | destructor: /* compiler magic */, 154 | // values for a 64-bit computer, halve them for 32-bit ones 155 | size: 24, 156 | align: 8, 157 | 158 | method: call_method_on_String as fn(*const ()) -> String, 159 | }; 160 | 161 | 其实这里的核心思想就是把函数调用替换成了函数指针,然后放到 vtable 里面,从而使得包含了 vtable 的 trait object 可以作为参数来控制最终调用函数的位置。 162 | vtable 里面的 destructor 字段指向一个函数,这个函数会清理掉 vtable 占用的资源,对 u8 来说这个字段为空,但 String 就需要它来释放内存。对占有类型的 trait object,比如 Box 来说这个 destructor 把自身由 Box 分配的空间 和 trait object 里面占用的资源一同清理掉。size 和 align 字段保存了被擦除类型的大小和对齐要求;这两个字段目前还没有用,将来会用到。 163 | 164 | 下面例举了编译器如何显示地构造和使用 trait object : 165 | 166 | let a: String = "foo".to_string(); 167 | let x: u8 = 1; 168 | 169 | // let b: &Foo = &a; 170 | let b = TraitObject { 171 | // store the data 172 | data: &a, 173 | // store the methods 174 | vtable: &Foo_for_String_vtable 175 | }; 176 | 177 | // let y: &Foo = x; 178 | let y = TraitObject { 179 | // store the data 180 | data: &x, 181 | // store the methods 182 | vtable: &Foo_for_u8_vtable 183 | }; 184 | 185 | // b.method(); 186 | (b.vtable.method)(b.data); 187 | 188 | // y.method(); 189 | (y.vtable.method)(y.data); 190 | 191 | 如果 b 或者 y 是持有类型的 trait object 的话,在离开作用域的时候还会有个 (b.vtable.destructor)(b.data) 调用。 -------------------------------------------------------------------------------- /syntax_semantics/pointer.md: -------------------------------------------------------------------------------- 1 | 指针 2 | === 3 | 4 | Rust 指针是其众多特性中格外引人注目的一个,当然,也格外困扰 Rust 新手。即使你拥有 C++ 背景也会有不同程度的困惑。这篇手册有助你理解指针这个重要主题。 5 | 6 | 我们这有个[速查表](http://doc.rust-lang.org/book/pointers.html#cheat-sheet)可以帮助你了解指针的类型、名称、作用。 7 | 8 | ##介绍 9 | 如果你不了解指针,这儿提供一个简介。指针在系统级编程语言里是非常基础的部分,理解它很重要。 10 | 11 | ##指针基础 12 | 当你创建值绑定的时候其实是在给栈上分配的一个值命名。(如果不知道什么是栈和堆请移步 [stackoverflow](http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap)) 例如: 13 | 14 | let x = 5i; 15 | let y = 8i; 16 | 17 | | location | value | 18 | |-----------|--------| 19 | | 0xd3e030 | 5 | 20 | | 0xd3e028 | 8 | 21 | 22 | 这里我们标记处了值的地址,`x` 对应于内存 0xd3e030 处 的 `5`。当我们提到 `x` 时,它的值就是 `5`. 23 | 24 | 接下来我们引入一个指针。在其他语言里,可能只有一种指针,但在 Rust 里我们有好几种,这儿我们使用 Rust 的*引用*,这是最简单的一种指针。 25 | 26 | let x = 5i; 27 | let y = 8i; 28 | let z = &y; 29 | 30 | 31 | | location | value | 32 | |-----------|----------| 33 | | 0xd3e030 | 5 | 34 | | 0xd3e028 | 8 | 35 | | 0xd3e020 | 0xd3e028 | 36 | 37 | 看到不同之处了吗?指针保存的是一个内存的一个地址,而不是值本身。在这里 `z` 保存的是 `y` 的地址。`x` , `y` 是 `int` 型,而 `z` 是 `&int` 型。我们可以用 `{:p}` 打印地址: 38 | 39 | println!("{:p}",z); 40 | 41 | 在我的机器上打印出 0xd3e028. 42 | 43 | 因为 `int` 和 `&int` 是不同的类型,我们不能将它们相加: 44 | 45 | println!("{}", x + z) 46 | 47 | 这会报错: 48 | 49 | hello.rs:6:24: 6:25 error: mismatched types: expected `int` but found `&int` (expected int but found &-ptr) 50 | hello.rs:6 println!("{}", x + z); 51 | ^ 52 | 我们可以用 `*` 符号解引用指针。解引用就是获取指针保存的地址指向的值, 53 | 54 | let x = 5i; 55 | let y = 8i; 56 | let z = &y; 57 | 58 | print!("{}", x + *z); 59 | 60 | 打印出 13. 61 | 62 | 好了,总结一下,指针就是指向某个内存地址。我们讨论完了什么是指针,接下来看一下为什么会有指针。 63 | 64 | 65 | 66 | ##指针的使用 67 | 68 | Rust 的指针非常有用,但用法上有别于其他语言。在讨论 Rust 指针的最佳实践之前我们来看看其他语言对指针的使用: 69 | 70 | 在 C 语言中字符串其实一个指向以 null 字符结尾的char数组的指针,使用字符串类型的唯一方式就是熟悉指针。 71 | 72 | 指针主要的作用就是指向非栈上的内存地址。前面的例子中使用两个栈上的变量,我们可以直接绑定名称。当我们在堆上分配内存的时候,我们就不能直接给它绑定名字了。在 c 里面使用 malloc 分配堆上内存,它返回一个指针。 73 | 74 | 综合前面讲的两点,每当你需要能动态改变大小的数据结构的时候就需要引入指针。编译器无法在编译的时候就分配堆上内存,所以我们编译的时候要用指针来指代它(将会在程序运行时候分配),在运行时也用这个指针来做一些操作。 75 | 76 | ##常见指针问题 77 | 78 | 我们已经讨论完指针并且指出了它的优点,那指针有哪些弊端呢?我们来看一下 Rust 一定程度上解决的指针问题: 79 | 80 | 未初始化的指针导致的问题,例如下面程序执行会发生什么就不好说了: 81 | 82 | &int x; 83 | *x = 5; 84 | 我们声明了一个指针,但没有指向任何位置,然后设置它指向位置的值为 5 。没有人知道这个没有指定的位置在哪,可能无关紧要也可能导致灾难性的后果。 85 | 86 | 当你结合函数使用指针的时候,非常容易让指针指向的内存变得无效,比如: 87 | 88 | func make_pointer(): &int { 89 | x = 5; 90 | return &x; 91 | } 92 | func main() { 93 | &int j = make_pointer(); 94 | *j = 5; // 错误! 95 | } 96 | 97 | x 是 make_pointer 函数的局部变量,在函数返回后立即失效(被释放)。但我们返回了一个指向它的指针,并且试图在 main 函数里使用这个指针,这种情况和使用未初始化的指针类似。后果不可预料。 98 | 99 | 最后一个问题,当多个指针指向同一个内存的时候会有问题。譬如: 100 | 101 | func mutate(&int i, int j) { 102 | *i = j; 103 | } 104 | 105 | func main() { 106 | x = 5; 107 | y = &x; 108 | z = &x; //y and z are aliased 109 | 110 | run_in_new_thread(mutate, y, 1); 111 | run_in_new_thread(mutate, z, 100); 112 | 113 | // what is the value of x here? 114 | } 115 | 116 | 这里的 run_in_new_thread 创建一个新线程,在新线程里调用 mutate 函数并传入参数。我们这有两个线程,它们都通过 metate 函数操作 x 的引用,我们不能确定哪个线程先完成,x 的值也就不确定了。还可能有更糟的情况,如果有一个引用指向了非法地址,我们就会像前面说到的那样的对一个非法地址进行操作,后果变得无法预料。 117 | 118 | ##Boxes 119 | 120 | `Box` 表示一个装箱指针(boxes pointer 在堆上分配数据返回指针): 121 | 122 | let x = Box::new(5); 123 | 124 | 虽然是在堆上分配,但是离开作用域后被自动释放: 125 | 126 | { 127 | let x = Box::new(5); 128 | 129 | // stuff happens 130 | 131 | } // 在这被释放 132 | 133 | box 并没有像其他语言里那样使用析构函数或者垃圾回收。Box 实际上是一个仿射类型(affine type)。也就是说编译器会检测 Box 的作用域范围,然后插入响应的代码。更进一步, Box 事实上是一种特殊的仿射类型叫做 [region](http://www.cs.umd.edu/projects/cyclone/papers/cyclone-regions.pdf)。 134 | 135 | 136 | 137 | ##总结 138 | 139 | 到这我们对指针有了个大概的认识。我们之前提到 Rust 设计了多种指针,并且避免了我们前面提到的问题。同时,Rust也会比其他只有一种指针类型的语言要复杂,鉴于 Rust 解决了简单指针的问题,这点复杂性是可以接受的。 140 | 141 | ##引用 142 | 143 | 引用是 Rust 里最基本的一种指针, 记作: 144 | 145 | let x = 5; 146 | let y = &x; 147 | 148 | println!("{}", *y); 149 | println!("{:p}", y); 150 | println!("{}", y); 151 | 152 | 我们称 y 是 x 的引用。第一个 println! 通过解引用符号 * 打印出 y 引用对象的值,第二个会打印出 y 指向对象的地址,这里用了一个 :p 指明是指针类型。第三个同样会打印出 y 引用对象的值,println! 会自动为我们解引用。 153 | 154 | 下面是一个含有引用类型参数的函数: 155 | 156 | fn succ(x: &i32) -> i32 { *x + 1 } 157 | 158 | 你可以用 & 符号创建引用,因此我们有两种方式调用这个函数: 159 | 160 | fn succ(x: &i32) -> i32 { *x + 1 } 161 | 162 | fn main() { 163 | let x = 5; 164 | let y = &x; 165 | println!("{}", succ(y)); 166 | println!("{}", succ(&x)); 167 | } 168 | 169 | 两次调用都会打印出 6. 170 | 171 | 实际上这里完全可以不用引用: 172 | 173 | fn succ(x: i32) -> i32 { x + 1 } 174 | 175 | 引用默认是不可变类型: 176 | 177 | let x = 5; 178 | let y = &x; 179 | 180 | *y = 5; // error: cannot assign to immutable borrowed content `*y` 181 | 182 | 同样: 183 | 184 | let x = 5; 185 | let y = &mut x; // error: cannot borrow immutable local variable `x` as mutable 186 | 187 | 不可变指针是可以起别名的: 188 | 189 | let x = 5; 190 | let y = &x; 191 | let z = &x; 192 | 193 | 可变指针则不可以: 194 | 195 | let mut x = 5; 196 | let y = &mut x; 197 | let z = &mut x; // error: cannot borrow `x` as mutable more than once at a time 198 | 199 | 因为提供了这些安全检查,在运行时引用和传统的之中呢并没有区别。这些安全检查都是在编译期进行的,所以没有任何额外的运行时开销。这种原理叫做 region pointers -- 逐渐进化成了我们现在的*生命周期*。 200 | 201 | ##模式和 ref 202 | 203 | 当你对指针指向的内容进行匹配的时候,直接匹配并不是最好选择, 204 | 205 | fn possibly_print(x: &Option) { 206 | match *x { 207 | // BAD: cannot move out of a `&` 208 | Some(s) => println!("{}", s) 209 | 210 | // GOOD: instead take a reference into the memory of the `Option` 211 | Some(ref s) => println!("{}", *s), 212 | None => {} 213 | } 214 | } 215 | 216 | 这里 `ref s` 表示一个 &String 类型,而不是 String。这在你只是想引用一个对象而不是完全占有的时候很有用。 217 | 218 | ##速查表 219 | 220 | 这儿有个所有 Rust 指针类型的概述 221 | 222 | |类型 |名称 |概述 | 223 | |-----------|-------------------|---------------------------------------| 224 | |`&T` |引用 |允许一个或者多个对象读 T | 225 | |`&mut T` |可变引用 |允许一个对象读写 T | 226 | |`Box` |Box |只允许一个持有者读写堆上分配的 T. | 227 | |`Rc` |引用计数指针 |可供多个对象读堆上分配的 T | 228 | |`Arc` |线程安全的引用计数指针 |同上但是线程安全 | 229 | |`*const T` |裸指针 |读 T 是危险的 | 230 | |`*mut T` |可变裸指针 |读写 T 是危险的 | 231 | 232 | ##相关资源 233 | 234 | * [Box API 文档]() 235 | 236 | * [所有权系统指南]() 237 | 238 | * [Cyclone paper on regions]() -------------------------------------------------------------------------------- /effective_rust/error.md: -------------------------------------------------------------------------------- 1 | 错误处理 2 | === 3 | 4 | 墨菲定律告诉我们只要有可能出错就一定会出错。对不可避免的事情有所规划是非常重要的。Rust 内置了各种错误处理机制。 5 | 6 | 程序出错主要分两种:failtures, panics。我们先说它们的不同点,然后再来讨论它们分别应该如何处理。最后讨论何如把 failtures 升级为 panics。 7 | 8 | ##Failture vs. Panic 9 | 10 | Rust 用两个专有名词来区分两种形式错误:failture 和 panic。failture 是指能够以某种方式恢复的错误。panic 是指无法恢复的错误。 11 | 12 | 那这里的“恢复”是什么意思呢?大多数情况下,我们都需要对出错的可能性有个预判。例如 `from_str` 函数: 13 | 14 | from_str("5"); 15 | 16 | 这个函数将传入的 string 类型的参数转化成另一种类型。因为传入的是 string 类型,我们无法确定转化一定成功,譬如对 17 | 18 | from_str("helloworld"); 19 | 20 | 我们就不知道该怎么转化。我们用这个函数的时候很清楚这个函数实际上只能将特定的一些参数转化,其他的参数则会出错。这种出错被称之为 failture。 21 | 22 | 除此之外还有一种完全不应该发生的或者我们没法恢复的错误。一个常见的例子 `assert!` 宏: 23 | 24 | assert!(x == 5); 25 | 26 | 我们用 assert! 表示判断总是对的。如果错了,就是很严重的错误,错到我们无法再继续执行下去。另一个例子是 `unreachable!` 宏: 27 | 28 | enum Event { 29 | NewRelease, 30 | } 31 | 32 | fn probability(_: &Event) -> f64 { 33 | // real implementation would be more complex, of course 34 | 0.95 35 | } 36 | 37 | fn descriptive_probability(event: Event) -> &'static str { 38 | match probability(&event) { 39 | 1.00 => "certain", 40 | 0.00 => "impossible", 41 | 0.00 ... 0.25 => "very unlikely", 42 | 0.25 ... 0.50 => "unlikely", 43 | 0.50 ... 0.75 => "likely", 44 | 0.75 ... 1.00 => "very likely", 45 | } 46 | } 47 | 48 | fn main() { 49 | std::io::println(descriptive_probability(NewRelease)); 50 | } 51 | 52 | 运行会报错: 53 | 54 | error: non-exhaustive patterns: `_` not covered [E0004] 55 | 56 | 尽管我们想表达的是获得从 0.0 到 1.0 的所有的可能性描述,但是 Rust 并不知道这个规则。我们还需要对预料之外的肯能行进行处理: 57 | 58 | use Event::NewRelease; 59 | 60 | enum Event { 61 | NewRelease, 62 | } 63 | 64 | fn probability(_: &Event) -> f64 { 65 | // real implementation would be more complex, of course 66 | 0.95 67 | } 68 | 69 | fn descriptive_probability(event: Event) -> &'static str { 70 | match probability(&event) { 71 | 1.00 => "certain", 72 | 0.00 => "impossible", 73 | 0.00 ... 0.25 => "very unlikely", 74 | 0.25 ... 0.50 => "unlikely", 75 | 0.50 ... 0.75 => "likely", 76 | 0.75 ... 1.00 => "very likely", 77 | _ => unreachable!() 78 | } 79 | } 80 | 81 | fn main() { 82 | println!("{}", descriptive_probability(NewRelease)); 83 | } 84 | 85 | 这里 '_' 这种条件永远不会触发,这里用 unreachable! 来处理,一旦触发就会返回 panic。 86 | 87 | ##使用 Option 和 Result 进行错误处理 88 | 89 | 最简单的错误处理方式就是使用 Option 类型,以我们之前提到的 `from_str()` 方法为例: 90 | 91 | pub fn from_str(s: &str) -> Option 92 | 93 | `from_str()` 方法返回一个 `Option` 类型,如果成功返回的是 `Some(value)`, 失败返回 `None` 94 | 95 | Option 只适用于最简单的场景,他并能给出出错信息,当我们需要出错信息的时候,我们就需要使用 Result 类型了,它的定义如下: 96 | 97 | enum Result { 98 | Ok(T), 99 | Err(E) 100 | } 101 | 102 | 这是个 Rust 自带类型。Ok(T) 表示成功, Err(E) 表示失败。大多数情况下我们都应该使用 Result, 除了前面提到的那些简单情形。下面举个例子: 103 | 104 | #[derive(Debug)] 105 | enum Version { Version1, Version2 } 106 | 107 | #[derive(Debug)] 108 | enum ParseError { InvalidHeaderLength, InvalidVersion } 109 | 110 | fn parse_version(header: &[u8]) -> Result { 111 | if header.len() < 1 { 112 | return Err(ParseError::InvalidHeaderLength); 113 | } 114 | match header[0] { 115 | 1 => Ok(Version::Version1), 116 | 2 => Ok(Version::Version2), 117 | _ => Err(ParseError::InvalidVersion) 118 | } 119 | } 120 | 121 | let version = parse_version(&[1, 2, 3, 4]); 122 | match version { 123 | Ok(v) => { 124 | println!("working with version: {:?}", v); 125 | } 126 | Err(e) => { 127 | println!("error parsing header: {:?}", e); 128 | } 129 | } 130 | 131 | 这里定义了一个 ParseError 枚举类型来表示各种可能的错误。 132 | 133 | ##panic! 134 | 135 | `panic!` 用于处理那些无法恢复的错误,一旦这样的错误发生,当前线程就会崩溃。 136 | 137 | panic!("boom"); 138 | 139 | 会报错: 140 | 141 | thread '
' panicked at 'boom', hello.rs:2 142 | 143 | 无法恢复的错误相对来说比较少见,请节制使用 `panic!`。 144 | 145 | ##把 failture 提升为 panic 146 | 147 | 在某些特殊情况下我们需要把 fail 提升为 panic。比如 `io::stdin().read_line(&mut buffer)` 在读取一行的时候如果出错会返回一个 `Result` 类型,我可以处理这个错误然后继续执行,但我们不需要处理而是想让程序直接终止,就要用 unwarp 方法: 148 | 149 | io::stdin().read_line(&mut buffer).unwrap(); 150 | 151 | unwrap 方法会调用 panic! 如果 Option 是 None 的话。也就是“把值返回给我,如果出错就崩溃”的意思。这种处理方式相比对错误类型进行匹配 要不可靠些。但有时候就需要让程序直接崩溃。 152 | 153 | 还有个比 unwrap 优雅一点的处理方式: 154 | 155 | let mut buffer = String::new(); 156 | let input = io::stdin().read_line(&mut buffer) 157 | .ok() 158 | .expect("Failed to read line"); 159 | 160 | ok() 会把 Result 转化成 Option, expect() 作用和 unwrap 一样但是可以传入一个信息参数,你可以利用这个参数来提醒自己什么错误发生了。 161 | 162 | ##try! 163 | 164 | 调用很多个 返回 Result 类型的函数时候,处理返回错误就变得非常啰嗦。这时候 try! 就能简化代码。在没有 try! 的时候我们写成: 165 | 166 | use std::fs::File; 167 | use std::io; 168 | use std::io::prelude::*; 169 | 170 | struct Info { 171 | name: String, 172 | age: i32, 173 | rating: i32, 174 | } 175 | 176 | fn write_info(info: &Info) -> io::Result<()> { 177 | let mut file = File::open("my_best_friends.txt").unwrap(); 178 | 179 | if let Err(e) = writeln!(&mut file, "name: {}", info.name) { 180 | return Err(e) 181 | } 182 | if let Err(e) = writeln!(&mut file, "age: {}", info.age) { 183 | return Err(e) 184 | } 185 | if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) { 186 | return Err(e) 187 | } 188 | 189 | return Ok(()); 190 | } 191 | 192 | 使用 try! 写成: 193 | 194 | use std::fs::File; 195 | use std::io; 196 | use std::io::prelude::*; 197 | 198 | struct Info { 199 | name: String, 200 | age: i32, 201 | rating: i32, 202 | } 203 | 204 | fn write_info(info: &Info) -> io::Result<()> { 205 | let mut file = try!(File::open("my_best_friends.txt")); 206 | 207 | try!(writeln!(&mut file, "name: {}", info.name)); 208 | try!(writeln!(&mut file, "age: {}", info.age)); 209 | try!(writeln!(&mut file, "rating: {}", info.rating)); 210 | 211 | return Ok(()); 212 | } 213 | 214 | 你只能在返回 Result 的函数里使用 try!, 也就意味你不能在 main 函数里用,因为 main 函数什么都不返回。 215 | 216 | try! 用了 [FromError](http://doc.rust-lang.org/1.0.0-beta/std/error/#the-fromerror-trait) 来判断当前错误应该返回什么。 -------------------------------------------------------------------------------- /syntax_semantics/trait.md: -------------------------------------------------------------------------------- 1 | Trait 2 | === 3 | 4 | 回顾一下我们是怎么实现方法的: 5 | 6 | struct Circle { 7 | x: f64, 8 | y: f64, 9 | radius: f64, 10 | } 11 | 12 | impl Circle { 13 | fn area(&self) -> f64 { 14 | std::f64::consts::PI * (self.radius * self.radius) 15 | } 16 | } 17 | 18 | trait 和它的区别在于先给出方法定义然后再实现: 19 | 20 | struct Circle { 21 | x: f64, 22 | y: f64, 23 | radius: f64, 24 | } 25 | 26 | struct Circle { 27 | x: f64, 28 | y: f64, 29 | radius: f64, 30 | } 31 | 32 | trait HasArea { 33 | fn area(&self) -> f64; 34 | } 35 | 36 | impl HasArea for Circle { 37 | fn area(&self) -> f64 { 38 | std::f64::consts::PI * (self.radius * self.radius) 39 | } 40 | } 41 | 42 | `trait` 代码块 和 `impl` 代码块很像,区别是我们只放了一个声明在里面。当我们要实现 trait 时,用 `impl Trait for Item` 句式 而不是 `impl Item` 43 | 44 | 这有什么用处呢?如果你还记得之前我们遇到的这个错误: 45 | 46 | error: binary operation `==` cannot be applied to type `T` 47 | 48 | 我们可以用 traits 来约束我们的模板。看下面这个例子: 49 | 50 | fn print_area(shape: T) { 51 | println!("This shape has an area of {}", shape.area()); 52 | } 53 | 54 | 编译会报错: 55 | 56 | error: type `T` does not implement any method in scope named `area` 57 | 58 | 因为 T 可以使任意类型,我们不能确定 T 实现了 area 方法。这时候我们就能在 T 模板后面加一个 trait 约束来确保 T 类型实现了 area 方法: 59 | 60 | fn print_area(shape: T) { 61 | println!("This shape has an area of {}", shape.area()); 62 | } 63 | 64 | 的意思就是“任意实现了 HasArea 这个 trait 的类型”。trait 里包含必须要实现的方法(这里是 area )声明,我们可以肯定实现了 HasArea 的类型一定有个 `.area()` 方法. 65 | 66 | 下面是完整地例子: 67 | 68 | trait HasArea { 69 | fn area(&self) -> f64; 70 | } 71 | 72 | struct Circle { 73 | x: f64, 74 | y: f64, 75 | radius: f64, 76 | } 77 | 78 | impl HasArea for Circle { 79 | fn area(&self) -> f64 { 80 | std::f64::consts::PI * (self.radius * self.radius) 81 | } 82 | } 83 | 84 | struct Square { 85 | x: f64, 86 | y: f64, 87 | side: f64, 88 | } 89 | 90 | impl HasArea for Square { 91 | fn area(&self) -> f64 { 92 | self.side * self.side 93 | } 94 | } 95 | 96 | fn print_area(shape: T) { 97 | println!("This shape has an area of {}", shape.area()); 98 | } 99 | 100 | fn main() { 101 | let c = Circle { 102 | x: 0.0f64, 103 | y: 0.0f64, 104 | radius: 1.0f64, 105 | }; 106 | 107 | let s = Square { 108 | x: 0.0f64, 109 | y: 0.0f64, 110 | side: 1.0f64, 111 | }; 112 | 113 | print_area(c); 114 | print_area(s); 115 | } 116 | 117 | 运行输出: 118 | 119 | This shape has an area of 3.141593 120 | This shape has an area of 1 121 | 122 | 这儿 print_area 就成了通用函数了,同时还确保参数必须是实现了 HasArea 的类型,如果我们传入 int 型作为参数: 123 | 124 | print_area(5); 125 | 126 | 会报错: 127 | 128 | error: failed to find an implementation of trait main::HasArea for int 129 | 130 | 目前为止我们只给 struct 实现过 trait,实际上你可以为任意类型实现 trait。包括 i32 类型; 131 | 132 | trait HasArea { 133 | fn area(&self) -> f64; 134 | } 135 | 136 | impl HasArea for i32 { 137 | fn area(&self) -> f64 { 138 | println!("this is silly"); 139 | 140 | *self as f64 141 | } 142 | } 143 | 144 | 5.area(); 145 | 146 | 虽然可以这么做,但不鼓励这么做。 147 | 148 | trait 还有两个约束,当我们把 trait 的实现放在一个 mod 时: 149 | 150 | mod shapes { 151 | use std::f64::consts; 152 | 153 | trait HasArea { 154 | fn area(&self) -> f64; 155 | } 156 | 157 | struct Circle { 158 | x: f64, 159 | y: f64, 160 | radius: f64, 161 | } 162 | 163 | impl HasArea for Circle { 164 | fn area(&self) -> f64 { 165 | consts::PI * (self.radius * self.radius) 166 | } 167 | } 168 | } 169 | 170 | fn main() { 171 | let c = shapes::Circle { 172 | x: 0.0f64, 173 | y: 0.0f64, 174 | radius: 1.0f64, 175 | }; 176 | 177 | println!("{}", c.area()); 178 | } 179 | 180 | 会报错: 181 | 182 | error: type `shapes::Circle` does not implement any method in scope named `area` 183 | 184 | 这时我们加上 use 引用就可以了: 185 | 186 | use shapes::HasArea; 187 | 188 | mod shapes { 189 | use std::f64::consts; 190 | 191 | pub trait HasArea { 192 | fn area(&self) -> f64; 193 | } 194 | 195 | pub struct Circle { 196 | pub x: f64, 197 | pub y: f64, 198 | pub radius: f64, 199 | } 200 | 201 | impl HasArea for Circle { 202 | fn area(&self) -> f64 { 203 | consts::PI * (self.radius * self.radius) 204 | } 205 | } 206 | } 207 | 208 | 209 | fn main() { 210 | let c = shapes::Circle { 211 | x: 0.0f64, 212 | y: 0.0f64, 213 | radius: 1.0f64, 214 | }; 215 | 216 | println!("{}", c.area()); 217 | } 218 | 219 | 这就意味着即使被人为 int 类型实现了 trait 只有你不 use 那个 trait ,那么他的实现就对你没有影响。 220 | 221 | 另外还有一个更严格的约束,trait 和需要实现这个 trait 的类型必须在同一个 crate 下面。我们能为 i32 实现 HasArea 是因为 HasArea 在我们的 crate 内。但是如果我们要实现 Float 这个 trait 就不行了,这个 trait 由 Rust 提供并不在 我们的 crate 内。 222 | 223 | 最后需要注意的是包含 trait 约束的 模板函数编译后会生成多个版本,看一下 print_area: 224 | 225 | fn print_area(shape: T) { 226 | println!("This shape has an area of {}", shape.area()); 227 | } 228 | 229 | fn main() { 230 | let c = Circle { ... }; 231 | 232 | let s = Square { ... }; 233 | 234 | print_area(c); 235 | print_area(s); 236 | } 237 | 238 | 分别以 c 和 s 为参数调用 print_area ,Rust 会根据参数类型生成两个不一样的函数,实际上代码看上去应该是这样: 239 | 240 | fn __print_area_circle(shape: Circle) { 241 | println!("This shape has an area of {}", shape.area()); 242 | } 243 | 244 | fn __print_area_square(shape: Square) { 245 | println!("This shape has an area of {}", shape.area()); 246 | } 247 | 248 | fn main() { 249 | let c = Circle { ... }; 250 | 251 | let s = Square { ... }; 252 | 253 | __print_area_circle(c); 254 | __print_area_square(s); 255 | } 256 | 257 | 这么做节省动态决定调用哪个的开销,但是也增大了程序的体积,应为我们多出一个同样功能的函数。 258 | 259 | ##回到 inverse 例子 260 | 261 | 在模板一章里我们讲到: 262 | 263 | fn inverse(x: T) -> Result { 264 | if x == 0.0 { return Err("x cannot be zero!".to_string()); } 265 | Ok(1.0 / x) 266 | } 267 | 268 | 编译报错: 269 | 270 | error: binary operation `==` cannot be applied to type `T` 271 | 272 | 这是因为不确定传入的 T 类型到底能不能比较,这里我们试着加上PartialEq trait 约束: 273 | 274 | fn inverse(x: T) -> Result { 275 | if x == 0.0 { return Err("x cannot be zero!".to_string()); } 276 | Ok(1.0 / x) 277 | } 278 | 279 | 但是报错了: 280 | 281 | error: mismatched types: 282 | expected `T`, 283 | found `_` 284 | (expected type parameter, 285 | found floating-point variable) 286 | 287 | 我们希望 T 能和 另一个实现了 PartialEq 的 T 类型比较,但这里却是 浮点型。这里就要把 trait 换成 Float 了: 288 | 289 | use std::num::Float; 290 | 291 | fn inverse(x: T) -> Result { 292 | if x == Float::zero() { return Err("x cannot be zero!".to_string()) } 293 | let one: T = Float::one(); 294 | Ok(one / x) 295 | } 296 | 297 | 我们要把 0,0 和 1.0 替换成Float trait 里对应的方法。f32 和 f64 类型都实现了 Float 所以就对了。 298 | 299 | println!("the inverse of {} is {:?}", 2.0f32, inverse(2.0f32)); 300 | println!("the inverse of {} is {:?}", 2.0f64, inverse(2.0f64)); 301 | 302 | println!("the inverse of {} is {:?}", 0.0f32, inverse(0.0f32)); 303 | println!("the inverse of {} is {:?}", 0.0f64, inverse(0.0f64)); 304 | -------------------------------------------------------------------------------- /syntax_semantics/ownership.md: -------------------------------------------------------------------------------- 1 | 所有权系统 2 | === 3 | 4 | 这篇将讲述 Rust 的所有权(ownership)系统。这是 Rust 最为特殊和引人入胜的部分,也是 Rust 程序员必须要掌握的。Rust 是通过*所有权*来实现内存安全的。所有权系统分为几个部分: 5 | 6 | * *所有权*(ownership) 7 | * *借贷*(borrowing) 8 | * *生命周期*(lifetimes) 9 | 10 | 我们会依次介绍。 11 | 12 | ##前言 13 | 14 | 在开始讨论之前我们先指出所有权系统两点需要注意的地方。 15 | 16 | Rust 的最大卖点就是兼顾安全和速度,并且这两项是通过零代价的抽象来实现的。所有权系统是零代价抽象的代表。我们即将开始的所有讨论都是在编译时完成的,并没有任何运行时开销。 17 | 18 | 说实话这个系统的实现确实有一点代价:牺牲了简易性,导致学习曲线陡峭。很多初学者都有一种和编译器作斗争的感觉,自己觉得明明能编译通过的程序却报错。这是由我们理解的所有权系统工作方式和实际的 Rust 实现有所不同引起了。当然这类编译问题会随着你对 Rust 所有权系统的了解逐步减少。 19 | 20 | ##所有权 21 | 22 | 所有权的核心是资源。资源有好多种(内存,硬盘上的文件等),方便起见这里我们只讨论最常用的一种资源:内存。 23 | 24 | 使用任何不提供垃圾回收的语言,只要你从堆上申请了内存就要负担起以销毁它的责任。比如一个 `foo` 函数分配了 4 字节的内存,然后一直没有销毁。这就会引起内存泄露,每次我们调用 `foo` 都会分配 4 字节。一定次数的调用之后,程序就会吃光我们的内存。这很糟糕,所以我们要以某种方式回收这 4 字节内存,另外我们也不能多回收。多次回收也会导致问题,这里先不说为什么。总之任何时候我们申请了内存,我们要确保回收那片内存并且只回收一次。多一次不行,少一次也不行。分配的次数和回收的次数要对应。 25 | 26 | 关于内存分配有个重要的细节。任何时候我们申请一块内存返回的都是指向这块内存的指针。我们通过这个指针对内存进行操作。只要指针在,我们就可以操作内存,一旦指针被销毁我们也失去了对内存操作的能力。 27 | 28 | 历来,系统编程语言都需要你手动追踪内存分配和释放。我们看一个 C 语言的例子: 29 | 30 | { 31 | int *x = malloc(sizeof(int)); // 申请内存 32 | *x = 5; 33 | free(x); // 释放内存 34 | } 35 | 36 | Rust 将资源的申请和释放设计成了一个整体,就是所有权系统。 37 | 38 | 我们先来看一种简单的情况。 39 | 40 | 我们用 Box::new() 申请堆上内存,得到一个指向该内存的引用。和 C 不同,Rust 会分析这个引用的作用域,离开当前作用域后,你就不能使用它进行任何操作,然后Rust负责帮你释放内存。上面的例子用 Rust 表示: 41 | 42 | { 43 | let x = Box::new(5); 44 | } 45 | 46 | 这里用 Box::new 在堆上分配了能放下一个 i32 的内存。我们前面说了,申请的内存必须释放,但是我们并没有看到有关释放内存的操作,实际上 Rust 自动帮你释放了。它知道 x 是 box 的一个引用,并且知道在代码块结束时 x 会离开当前作用域,于是 Rust 编译器会在代码块最后插入释放内存的代码。这些操作都是编译器替我们完成的,不用担心会忘记,也不用担心分配和释放的次数不匹配。 47 | 48 | 这比较简单,但是如果我们要把我们的 box 传给另一个函数呢?你们也许会这样写: 49 | 50 | fn main() { 51 | let x = Box::new(5); 52 | add_one(x); 53 | } 54 | 55 | fn add_one(mut num: Box) { 56 | *num += 1; 57 | } 58 | 59 | 这段代码能运行,但存在问题。假设我们要加一行代码,打印出 x 的值: 60 | 61 | fn main() { 62 | let x = Box::new(5); 63 | add_one(x); 64 | println!("{}", x); 65 | } 66 | 67 | fn add_one(mut num: Box) { 68 | *num += 1; 69 | } 70 | 71 | 尝试编译的话,会得到一个错误: 72 | 73 | error: use of moved value: `x` 74 | println!("{}", x); 75 | ^ 76 | 77 | 每个内存申请都要对应一个内存释放。当我们把 box 传交给 add_one 时,将会有两个指针指向同一块内存:main 函数中的 x 和 add_one 函数中的 num。如果我们还是向前面说的,在指针离开作用域就释放内存,那么这块内存将会被释放两次,这会导致程序出错。因此 Rust 规定一个资源只能有一个拥有者,所以当我们调用 add_one 的时候 Rust 会解除 x 对 box 的所有权,然后将 num 设为拥有者,x 不再拥有 box 资源,于是有了那个错误:use of moved value: `x`。 78 | 79 | 一种解决这个错误的方式是让 num 在用完资源后再把资源转交,怎么转交呢?返回一个 box 就行了: 80 | 81 | fn main() { 82 | let x = Box::new(5); 83 | let y = add_one(x); 84 | println!("{}", y); 85 | } 86 | 87 | fn add_one(mut num: Box) -> Box { 88 | *num += 1; 89 | num 90 | } 91 | 92 | 这样就可以正常运行了。我们返回了一个完整的 box 而不是指向他的指针,资源转交给了 y 。仔细想一下, add_one 只是临时地用一下 box 而已,没必要完全将 box 转交给他。Rust 为此引入了一个*借贷*的概念,专指将资源临时借给别人但我还是拥有者的情况。Rust 里面的引用 '&' 可以用来表示借贷。 93 | 94 | ##借贷 95 | 96 | 再来看一下当前版本的 add_one 函数: 97 | 98 | fn add_one(mut num: Box) -> Box { 99 | *num += 1; 100 | num 101 | } 102 | 103 | 这个函数传入的是 box (堆上分配的资源),一个资源不能同时有两个拥有者,所以 box 被转交给了 num。最后函数返回一个 box 又将资源转交出去了。(转交给你就是你的了,英国将香港转交给中国,香港就是中国的了,当然香港本来就是中国的) 104 | 105 | 生活中,我们常把东西借给别人用一段时间。这东西还是我的,我只是借给人家用而已。东西在谁那,谁就有责任保管好,所以东西借给他,保管责任也转移到他那边。 106 | 107 | Rust 的所有权系统于此类似,允许拥有者将资源临时的借给别人。下面是一个借贷版本的 add_one,你可以和转交版本的对比一下: 108 | 109 | fn add_one(num: &mut i32) { 110 | *num += 1; 111 | } 112 | 113 | 这个版本从调用者那边借贷来一个 i32 ,然后递增一次。函数结束,num 被释放,借贷关系终结。 114 | 115 | 相应我们也要改一下 main 函数: 116 | 117 | fn main() { 118 | let mut x = 5; 119 | add_one(&mut x); 120 | println!("{}", x); 121 | } 122 | 123 | fn add_one(num: &mut i32) { 124 | *num += 1; 125 | } 126 | 127 | 这里我们只是临时借贷 box ,并不需要像之前通过返回 box 转交来转交去了那么麻烦了。 128 | 129 | ##生命周期 130 | 131 | 如果你已经理解了上面的内容,我们再来看一些更复杂的情况: 132 | 133 | 1.A 申请了一块资源 134 | 2.A 把这资源以引用方式借贷给 B 135 | 3.A 用完了资源决定销毁,而此时 B 的借贷关系并没有解除 136 | 4.B 又要用资源了 137 | 138 | 因为在 B 用资源之前,资源已经被释放掉了,这就会出现危险指针错误或者叫 use after free错误。 139 | 140 | 为了解决这个问题,我们要确保 4 事件永不会在 3 事件之后进行。为此 Rust 引入了*生命周期*的概念,生命周期是指借贷关系的生命周期。在 B 使用资源的时候 Rust 会检查 B 的借贷关系的生命周期。 141 | 142 | 我们再来看一下 add_one 函数: 143 | 144 | fn add_one(num: &i32) -> i32 { 145 | *num + 1 146 | } 147 | 148 | 这里其实省略了 num 的生命周期,因为能够被推断出来: 149 | 150 | fn add_one<'a>(num: &'a i32) -> i32 { 151 | *num + 1 152 | } 153 | 154 | 'a 就叫做生命周期,生命经常用 'a 'b 'c 表示,简单清晰。我们分析一下句法: 155 | 156 | fn add_one<'a>(...) 157 | 158 | 这句的意思是 add_one 有一个叫做 'a 的生命周期。如果我们需要两个可以写成: 159 | 160 | fn add_two<'a, 'b>(...) 161 | 162 | 在参数列表里我们使用了刚声明的生命周期: 163 | 164 | ...(num: &'a i32) -> ... 165 | 166 | 对比一下 `&i32` 和 `&'a i32` 可知只是在 & 和 i32 之间加了个生命周期 'a。`&i32` 读作“一个 i32 类型的引用“,`&'a i32` 读作“一个具有 'a 生命周期的 i32类型的引用”。 167 | 168 | ##作用域 169 | 170 | 最直观的理解生命周期的方法是把作用域内引用的生命周期画出来,例如: 171 | 172 | fn main() { 173 | let y = &5; // -+ y 的生命周期从这开始 174 | // | 175 | //do sth // | 176 | // | 177 | } // -+ y 的生命周期在这结束 178 | 179 | 添加一个 Foo 方法: 180 | 181 | struct Foo<'a> { 182 | x: &'a i32, 183 | } 184 | 185 | fn main() { 186 | let y = &5; // -+ y 的生命周期从这开始 187 | let f = Foo { x: y }; // -+ f 的生命周期从这开始 188 | //do sth // | 189 | // | 190 | } // -+ f 和 y 的生命周期在这结束 191 | 192 | f 的生命周期包含在 y 的生命周期内,这儿没有问题;可是一旦把一个短生命周期的对象绑定到一个常生命周期的对象上问题就来了: 193 | 194 | struct Foo<'a> { 195 | x: &'a i32, 196 | } 197 | 198 | fn main() { 199 | let x; // -+ x goes into scope 200 | // | 201 | { // | 202 | let y = &5; // ---+ y goes into scope 203 | let f = Foo { x: y }; // ---+ f goes into scope 204 | x = &f.x; // | | error here 205 | } // ---+ f and y go out of scope 206 | // | 207 | println!("{}", x); // | 208 | } // -+ x goes out of scope 209 | 210 | 这儿 x 的生命周期最长,f 的生命周期最短。但我们把 f 的一个成员绑定到了 x,这会导致 f 已经销毁了我们还在使用绑定到 x 的这个 f 的字段。 211 | 212 | 我们可以给这些声明周期命名。 213 | ##'static 214 | 215 | 有一种特殊的生命周期叫做 'static。它表示对象的生命周期是整个程序的运行时。我们第一次遇到 'static 是在使用字符串常量的时候: 216 | 217 | let x: &'stattic str = "hello, world"; 218 | 219 | 另外一个例子是全局变量: 220 | 221 | static FOO: i32 = 5; 222 | let x: &'static i32 = &FOO; 223 | 224 | 这会添加一个 i32 类型到程序的数据段上,x 是它的一个引用。 225 | 226 | #共享声明周期 227 | 228 | 到目前为止我们都是举得一个资源只有一个所有者的例子。但有时这并不合理。不如车有四个轮子,我们要把四个轮子都赋给一辆车: 229 | 230 | struct Car { 231 | name: String, 232 | } 233 | 234 | struct Wheel { 235 | size: i32, 236 | owner: Car, 237 | } 238 | 239 | fn main() { 240 | let car = Car { name: "DeLorean".to_string() }; 241 | 242 | for _ in 0..4 { 243 | Wheel { size: 360, owner: car }; 244 | } 245 | } 246 | 247 | 会报错: 248 | 249 | error: use of moved value: `car` 250 | Wheel { size: 360, owner: car }; 251 | ^~~ 252 | note: `car` moved here because it has type `Car`, which is non-copyable 253 | Wheel { size: 360, owner: car }; 254 | ^~~ 255 | 256 | 这里我们需要 Car 能够被多个 Wheel 引用,Box 不行因为它只能有一个持有者,这里我们要用 Rc: 257 | 258 | use std::rc::Rc; 259 | 260 | struct Car { 261 | name: String, 262 | } 263 | 264 | struct Wheel { 265 | size: i32, 266 | owner: Rc, 267 | } 268 | 269 | fn main() { 270 | let car = Car { name: "DeLorean".to_string() }; 271 | 272 | let car_owner = Rc::new(car); 273 | 274 | for _ in 0..4 { 275 | Wheel { size: 360, owner: car_owner.clone() }; 276 | } 277 | } 278 | 279 | 这我们把 Car 放在了 Rc 里面,得到一个 Rc,然后使用 clone() 方法来创建一个新的引用。 280 | 281 | 这是最简单的多人持有的情况。当我们需要考虑到多线程时,要用 Arc ,这个相对于 Rc 它的引用计数是线程安全的。 282 | 283 | #生命周期的省略 284 | 285 | 我们引入两个术语来讨论生命周期的省略,一个叫输入生命周期(input lifetime),一个叫输出生命周期(output lifetime)。输入生命周期和函数的参数绑定,输出生命周期和函数返回值绑定。例如: 286 | 287 | fn foo<'a>(bar: &'a str) // 有个输入生命周期 288 | 289 | fn foo<'a>() -> &'a str // 有个输出生命周期 290 | 291 | fn foo<'a>(bar: &'a str) -> &'a str // 输入输出生命周期都有 292 | 293 | 这儿有三条规则: 294 | 295 | * 编译器会把函数各个参数省略掉的生命周期变成不同的生命周期参数。 296 | * 如果仅有一个输入生命周期,不管它省略与否,所有输出生命周期和这个输入生命周期保持一致。 297 | * 如果有多个输入生命周期,其中有一个是 `&self` 或者 `&mut self`,那么所有省略掉的输出生命周期都被设成和 self 一致。 298 | 299 | #例子 300 | 301 | fn print(s: &str); // 省略形式 302 | fn print<'a>(s: &'a str); // 完整形式 303 | 304 | fn debug(lvl: u32, s: &str); // 省略形式 305 | fn debug<'a>(lvl: u32, s: &'a str); // 完整形式 306 | 307 | // In the preceding example, `lvl` doesn't need a lifetime because it's not a 308 | // reference (`&`). Only things relating to references (such as a `struct` 309 | // which contains a reference) need lifetimes. 310 | 311 | fn substr(s: &str, until: u32) -> &str; // 省略形式 312 | fn substr<'a>(s: &'a str, until: u32) -> &'a str; // 完整形式 313 | 314 | fn get_str() -> &str; // ILLEGAL, no inputs 315 | 316 | fn frob(s: &str, t: &str) -> &str; // ILLEGAL, two inputs 317 | fn frob<'a, 'b>(s: &'a str, t: &'b str) -> &str; // 完整形式: Output lifetime is unclear 318 | 319 | fn get_mut(&mut self) -> &mut T; // 省略形式 320 | fn get_mut<'a>(&'a mut self) -> &'a mut T; // 完整形式 321 | 322 | fn args(&mut self, args: &[T]) -> &mut Command // 省略形式 323 | fn args<'a, 'b, T:ToCStr>(&'a mut self, args: &'b [T]) -> &'a mut Command // 完整形式 324 | 325 | fn new(buf: &mut [u8]) -> BufWriter; // 省略形式 326 | fn new<'a>(buf: &'a mut [u8]) -> BufWriter<'a> // 完整形式 --------------------------------------------------------------------------------