介绍
-ch01-00-introduction.md
+commit 4f2dc564851dc04b271a2260c834643dfd86c724
-commit c51c14215d2ee2cb481bc8a942a3769c6d9a2e1a
欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的人们。
Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:有可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package diff --git a/docs/ch01-01-installation.html b/docs/ch01-01-installation.html index 5ffcf08..0d562e6 100644 --- a/docs/ch01-01-installation.html +++ b/docs/ch01-01-installation.html @@ -69,9 +69,9 @@
安装
-ch01-01-installation.md
+commit 4f2dc564851dc04b271a2260c834643dfd86c724
-commit f828919e62aa542aaaae03c1fb565da42374213e
使用 Rust 的第一步是安装。你需要联网来执行本章的命令,因为我们要从网上下载 Rust。
我们将会展示很多使用终端的命令,并且这些代码都以$
开头。并不需要真正输入$
,它们在这里代表每行指令的开头。在网上会看到很多使用这个惯例的教程和例子:$
代表以常规用户运行命令,#
代表需要用管理员运行的命令。没有以$
(或#
)的行通常是之前命令的输出。
Hello, World!
-ch01-02-hello-world.md
+commit 4f2dc564851dc04b271a2260c834643dfd86c724
-commit ccbeea7b9fe115cd545881618fe14229d18b307f
现在你已经安装好了 Rust,让我们来编写你的第一个 Rust 程序。当学习一门新语言的时候,编写一个在屏幕上打印 “Hello, world!” 文本的小程序是一个传统,而在这一部分,我们将遵循这个传统。
diff --git a/docs/ch13-02-iterators.html b/docs/ch13-02-iterators.html index 983e24f..3e31b12 100644 --- a/docs/ch13-02-iterators.html +++ b/docs/ch13-02-iterators.html @@ -155,7 +155,68 @@ impl Iterator for Counter {
type Item = u32
这一行表明迭代器中Item
的关联类型将是u32
。同样无需担心关联类型,因为第XX章会涉及他们。-
next
方法是迭代器的主要接口,它返回一个Option
。如果它是Some(value)
,相当于可以迭代器中获取另一个值。如果它是None
,迭代器就结束了。在next
方法中可以进行任何迭代器需要的计算。在这个例子中,我们对当前状态加一,接着检查其是否仍然小于六。如果是,返回Some(self.count)
来产生下一个值。如果大于等于六,迭代结束并返回None
。迭代器 trait 指定当其返回
+None
,就代表迭代结束。该 trait 并不强制任何在next
方法返回None
后再次调用时必须有的行为。在这个情况下,在第一次返回None
后每一次调用next
仍然返回None
,不过其内部count
字段会依次增长到u32
的最大值,接着count
会溢出(在调试模式会panic!
而在发布模式则会折叠从最小值开始)。迭代器 trait 指定当其返回
+None
,就代表迭代结束。该 trait 并不强制任何在next
方法返回None
后再次调用时必须有的行为。在这个情况下,在第一次返回None
后每一次调用next
仍然返回None
,不过其内部count
字段会依次增长到u32
的最大值,接着count
会溢出(在调试模式会panic!
而在发布模式则会折叠从最小值开始)。有些其他的迭代器则选择再次从头开始迭代。如果需要确保迭代器在返回第一个None
之后所有的next
方法调用都返回None
,可以使用fuse
方法来创建不同于任何其他的迭代器。一旦实现了
+Iterator
trait,我们就有了一个迭代器!可以通过不停的调用Counter
结构体的next
方法来使用迭代器的功能:+let mut counter = Counter::new(); + +let x = counter.next(); +println!("{:?}", x); + +let x = counter.next(); +println!("{:?}", x); + +let x = counter.next(); +println!("{:?}", x); + +let x = counter.next(); +println!("{:?}", x); + +let x = counter.next(); +println!("{:?}", x); + +let x = counter.next(); +println!("{:?}", x); +
这会一次一行的打印出从
+Some(1)
到Some(5)
,之后就全是None
。各种
+Iterator
适配器在列表 13-5 中有一个迭代器并调用了其像
+map
和collect
这样的方法。然而在列表 13-6 中,只实现了Counter
的next
方法。Counter
如何才能得到像map
和collect
这样的方法呢?好吧,当讲到
+Iterator
的定义时,我们故意省略一个小的细节。Iterator
定义了一系列默认实现,他们会调用next
方法。因为next
是唯一一个Iterator
trait 没有默认实现的方法,一旦实现之后,Iterator
的所有其他的适配器就都可用了。这些适配器可不少!例如,处于某种原因我们希望获取一个
+Counter
实例产生的头五个值,与另一个Counter
实例第一个之后的值相组合,将每组数相乘,并只保留能被三整除的相乘结果,最后将保留结果相加,我们可以这么做:+# struct Counter { +# count: u32, +# } +# +# impl Counter { +# fn new() -> Counter { +# Counter { count: 0 } +# } +# } +# +# impl Iterator for Counter { +# // Our iterator will produce u32s +# type Item = u32; +# +# fn next(&mut self) -> Option<Self::Item> { +# // increment our count. This is why we started at zero. +# self.count += 1; +# +# // check to see if we've finished counting or not. +# if self.count < 6 { +# Some(self.count) +# } else { +# None +# } +# } +# } +let sum: u32 = Counter::new().take(5) + .zip(Counter::new().skip(1)) + .map(|(a, b)| a * b) + .filter(|x| x % 3 == 0) + .sum(); +assert_eq!(18, sum); +
注意
+zip
只生成四对值;理论上的第五对值并不会产生,因为zip
在任一输入返回None
时也会返回None
(这个迭代器最多就生成 5)。因为实现了
Iterator
的next
方法,所有这些方法调用都是可能的。请查看标准库文档来寻找迭代器可能会用得上的方法。
改进 I/O 项目
+++ch13-03-improving-our-io-project.md +
+
+commit 4f2dc564851dc04b271a2260c834643dfd86c724
在我们上一章实现的grep
I/O 项目中,其中有一些地方的代码可以使用迭代器来变得更清楚简洁一些。让我们看看迭代器如何能够改进Config::new
函数和grep
函数的实现。
使用迭代器并去掉clone
+回到列表 12-8 中,这些代码获取一个String
slice 并创建一个Config
结构体的实例,它检查参数的数量、索引 slice 中的值、并克隆这些值以便Config
可以拥有他们的所有权:
impl Config {
+ fn new(args: &[String]) -> Result<Config, &'static str> {
+ if args.len() < 3 {
+ return Err("not enough arguments");
+ }
+
+ let search = args[1].clone();
+ let filename = args[2].clone();
+
+ Ok(Config {
+ search: search,
+ filename: filename,
+ })
+ }
+}
+
+当时我们说不必担心这里的clone
调用,因为将来会移除他们。好吧,就是现在了!所以,为什么这里需要clone
呢?这里的问题是参数args
中有一个String
元素的 slice,而new
函数并不拥有args
。为了能够返回Config
实例的所有权,我们需要克隆Config
中字段search
和filename
的值,这样Config
就能拥有这些值了。
现在在认识了迭代器之后,我们可以将new
函数改为获取一个有所有权的迭代器作为参数。可以使用迭代器来代替之前必要的 slice 长度检查和特定位置的索引。因为我们获取了迭代器的所有权,就不再需要借用所有权的索引操作了,我们可以直接将迭代器中的String
值移动到Config
中,而不用调用clone
来创建一个新的实例。
首先,让我们看看列表 12-6 中的main
函数,将env::args
的返回值改为传递给Config::new
,而不是调用collect
并传递一个 slice:
fn main() {
+ let config = Config::new(env::args());
+ // ...snip...
+
+
+如果参看标准库中env::args
函数的文档,我们会发现它的返回值类型是std::env::Args
。所以下一步就是更新Config::new
的签名使得参数args
拥有std::env::Args
类型而不是&[String]
:
impl Config {
+ fn new(args: std::env::Args) -> Result<Config, &'static str> {
+ // ...snip...
+
+
+之后我们将修复Config::new
的函数体。因为标准库文档也表明,std::env::Args
实现了Iterator
trait,所以我们知道可以调用其next
方法!如下就是新的代码:
# struct Config {
+# search: String,
+# filename: String,
+# }
+#
+impl Config {
+ fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
+ args.next();
+
+ let search = match args.next() {
+ Some(arg) => arg,
+ None => return Err("Didn't get a search string"),
+ };
+
+ let filename = match args.next() {
+ Some(arg) => arg,
+ None => return Err("Didn't get a file name"),
+ };
+
+ Ok(Config {
+ search: search,
+ filename: filename,
+ })
+ }
+}
+
+
+还记得env::args
返回值的第一个值是程序的名称吗。我们希望忽略它,所以首先调用next
并不处理其返回值。第二次调用next
的返回值应该是希望放入Config
中search
字段的值。使用match
来在next
返回Some
时提取值,而在因为没有足够的参数(这会造成next
调用返回None
)而提早返回Err
值。
对filename
值也进行相同处理。稍微有些可惜的是search
和filename
的match
表达式是如此的相似。如果可以对next
返回的Option
使用?
就好了,不过目前?
只能用于Result
值。即便我们可以像Result
一样对Option
使用?
,得到的值也是借用的,而我们希望能够将迭代器中的String
移动到Config
中。
使用迭代器适配器来使代码更简明
+另一部分可以利用迭代器的代码位于列表 12-15 中实现的grep
函数中:
fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+ let mut results = Vec::new();
+
+ for line in contents.lines() {
+ if line.contains(search) {
+ results.push(line);
+ }
+ }
+
+ results
+}
+
+我们可以用一种更简短的方式来编写这些代码,并避免使用了一个作为可变中间值的results
vector,像这样使用迭代器适配器方法来实现:
fn grep<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+ contents.lines()
+ .filter(|line| line.contains(search))
+ .collect()
+}
+
+这里使用了filter
适配器来只保留line.contains(search)
为真的那些行。接着使用collect
将他们放入另一个 vector 中。这就简单多了!
也可以对列表 12-16 中定义的grep_case_insensitive
函数使用如下同样的技术:
fn grep_case_insensitive<'a>(search: &str, contents: &'a str) -> Vec<&'a str> {
+ let search = search.to_lowercase();
+
+ contents.lines()
+ .filter(|line| {
+ line.to_lowercase().contains(&search)
+ }).collect()
+}
+
+看起来还不坏!那么到底该用哪种风格呢?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了直觉上的理解之后,他们将更加容易理解。相比使用很多看起来大同小异的循环并创建一个 vector,抽象出这些老生常谈的代码将使得我们更容易看清代码所特有的概念,比如迭代器中用于过滤每个元素的条件。
+不过他们真的完全等同吗?当然更底层的循环会更快一些。让我们聊聊性能吧。
+