From 606928c36a635866160ecc9b5c5615efa53a3883 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Thu, 9 Mar 2017 23:53:47 +0800 Subject: [PATCH] wip finish ch13 --- docs/ch01-00-introduction.html | 4 +- docs/ch01-01-installation.html | 4 +- docs/ch01-02-hello-world.html | 4 +- docs/ch13-02-iterators.html | 63 +++++- docs/ch13-03-improving-our-io-project.html | 110 +++++++++- docs/ch13-04-performance.html | 43 +++- docs/index.html | 4 +- docs/print.html | 224 ++++++++++++++++++++- src/ch01-00-introduction.md | 4 +- src/ch01-01-installation.md | 4 +- src/ch01-02-hello-world.md | 4 +- src/ch13-02-iterators.md | 76 ++++++- src/ch13-03-improving-our-io-project.md | 143 +++++++++++++ src/ch13-04-performance.md | 51 +++++ 14 files changed, 713 insertions(+), 25 deletions(-) diff --git a/docs/ch01-00-introduction.html b/docs/ch01-00-introduction.html index 4476c80..c8ee78a 100644 --- a/docs/ch01-00-introduction.html +++ b/docs/ch01-00-introduction.html @@ -69,9 +69,9 @@

介绍

-

ch01-00-introduction.md +

ch01-00-introduction.md
-commit c51c14215d2ee2cb481bc8a942a3769c6d9a2e1a

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

欢迎阅读“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 +

ch01-01-installation.md
-commit f828919e62aa542aaaae03c1fb565da42374213e

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

使用 Rust 的第一步是安装。你需要联网来执行本章的命令,因为我们要从网上下载 Rust。

我们将会展示很多使用终端的命令,并且这些代码都以$开头。并不需要真正输入$,它们在这里代表每行指令的开头。在网上会看到很多使用这个惯例的教程和例子:$代表以常规用户运行命令,#代表需要用管理员运行的命令。没有以$(或#)的行通常是之前命令的输出。

diff --git a/docs/ch01-02-hello-world.html b/docs/ch01-02-hello-world.html index fce5109..b8f37ce 100644 --- a/docs/ch01-02-hello-world.html +++ b/docs/ch01-02-hello-world.html @@ -69,9 +69,9 @@

Hello, World!

-

ch01-02-hello-world.md +

ch01-02-hello-world.md
-commit ccbeea7b9fe115cd545881618fe14229d18b307f

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

现在你已经安装好了 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 中有一个迭代器并调用了其像mapcollect这样的方法。然而在列表 13-6 中,只实现了Counternext方法。Counter如何才能得到像mapcollect这样的方法呢?

+

好吧,当讲到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)。

+

因为实现了Iteratornext方法,所有这些方法调用都是可能的。请查看标准库文档来寻找迭代器可能会用得上的方法。

diff --git a/docs/ch13-03-improving-our-io-project.html b/docs/ch13-03-improving-our-io-project.html index 2aac27d..2d5b0e1 100644 --- a/docs/ch13-03-improving-our-io-project.html +++ b/docs/ch13-03-improving-our-io-project.html @@ -67,7 +67,115 @@
- +

改进 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中字段searchfilename的值,这样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的返回值应该是希望放入Configsearch字段的值。使用match来在next返回Some时提取值,而在因为没有足够的参数(这会造成next调用返回None)而提早返回Err值。

+

filename值也进行相同处理。稍微有些可惜的是searchfilenamematch表达式是如此的相似。如果可以对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,抽象出这些老生常谈的代码将使得我们更容易看清代码所特有的概念,比如迭代器中用于过滤每个元素的条件。

+

不过他们真的完全等同吗?当然更底层的循环会更快一些。让我们聊聊性能吧。

+
diff --git a/docs/ch13-04-performance.html b/docs/ch13-04-performance.html index d139631..d78c2cf 100644 --- a/docs/ch13-04-performance.html +++ b/docs/ch13-04-performance.html @@ -67,7 +67,48 @@
- +

性能

+
+

ch13-04-performance.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

哪一个版本的grep函数会更快一些呢:是直接使用for循环的版本还是使用迭代器的版本呢?我们将运行一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进String并寻找其中的单词 "the"。如下是for循环版本和迭代器版本的 grep 函数的性能测试结果:

+
test bench_grep_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
+test bench_grep_iter ... bench:  19,234,900 ns/iter (+/- 657,200)
+
+

结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,光是这一点并不是为了证明他们是完全等同的,而是提供了一个大体上比较这两种实现的方向。对于真正的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的零成本抽象zero-cost abstractions)之一,它意味着抽象并不会强加运行时开销,它与本贾尼·斯特劳斯特卢普,C++ 的设计和实现者所定义的零开销zero-overhead)如出一辙:

+
+

In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

+
    +
  • Bjarne Stroustrup "Foundations of C++"
  • +
+

从整体来说,C++ 的实现遵循了零开销原则:你不需要的,无需为他们买单。更有甚者的是:你需要的时候,也不可能找到其他更好的代码了。

+
    +
  • 本贾尼·斯特劳斯特卢普 "Foundations of C++"
  • +
+
+

作为另一个例子,这里有一些来自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫buffer的数据 slice、一个 12 个系数列表的coefficients、和一个移位位数的qlp_shift。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简洁的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:

+
let buffer: &mut [i32];
+let coefficients: [i64; 12];
+let qlp_shift: i16;
+
+for i in 12..buffer.len() {
+    let prediction = coefficients.iter()
+                                 .zip(&buffer[i - 12..i])
+                                 .map(|(&c, &s)| c * s as i64)
+                                 .sum::<i64>() >> qlp_shift;
+    let delta = buffer[i];
+    buffer[i] = prediction as i32 + delta;
+}
+
+

为了计算prediction的值,这些代码遍历了coefficients中的 12 个值,使用zip方法将系数与buffer的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移qlp_shift位。

+

像音频解码器这样的程序通常非常看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历coefficients的值完全用不到循环:Rust 知道这里会迭代 12 次,所以它“展开”了循环。所有的系数都被储存在了寄存器中(这意味着访问他们非常快)。也没有数组访问边界检查。这是极端有效率的。

+

现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。

+

总结

+

闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。他们对 Rust 直白的表达高级概念的能力有很大贡献。闭包和迭代器的实现,以及 Rust 的零成本抽象,也使得运行时性能不受影响。

+

现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多cargo的功能,他们是如何将项目准备好分享给世界的。

+
diff --git a/docs/index.html b/docs/index.html index 79665f6..155f933 100644 --- a/docs/index.html +++ b/docs/index.html @@ -68,9 +68,9 @@

介绍

-

ch01-00-introduction.md +

ch01-00-introduction.md
-commit c51c14215d2ee2cb481bc8a942a3769c6d9a2e1a

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的人们。

Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:有可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package diff --git a/docs/print.html b/docs/print.html index 0db39a8..f289140 100644 --- a/docs/print.html +++ b/docs/print.html @@ -69,9 +69,9 @@

介绍

-

ch01-00-introduction.md +

ch01-00-introduction.md
-commit c51c14215d2ee2cb481bc8a942a3769c6d9a2e1a

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的人们。

Rust 在编译时进行其绝大多数的安全检查和内存管理决策,因此程序的运行时性能没有受到影响。这让其在许多其他语言不擅长的应用场景中得以大显身手:有可预测空间和时间要求的程序,嵌入到其他语言中,以及编写底层代码,如设备驱动和操作系统。Rust 也很擅长 web 程序:它驱动着 Rust 包注册网站(package @@ -81,9 +81,9 @@ registry site),crates.io!我们期待看

本书是开源的。如果你发现任何错误,请不要犹豫,在 GitHub 上发起 issue 或提交 pull request。

安装

-

ch01-01-installation.md +

ch01-01-installation.md
-commit f828919e62aa542aaaae03c1fb565da42374213e

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

使用 Rust 的第一步是安装。你需要联网来执行本章的命令,因为我们要从网上下载 Rust。

我们将会展示很多使用终端的命令,并且这些代码都以$开头。并不需要真正输入$,它们在这里代表每行指令的开头。在网上会看到很多使用这个惯例的教程和例子:$代表以常规用户运行命令,#代表需要用管理员运行的命令。没有以$(或#)的行通常是之前命令的输出。

@@ -120,9 +120,9 @@ commit f828919e62aa542aaaae03c1fb565da42374213e

任何你太确认标准库提供的类型或函数是干什么的时候,使用文档 API 查找!

Hello, World!

-

ch01-02-hello-world.md +

ch01-02-hello-world.md
-commit ccbeea7b9fe115cd545881618fe14229d18b307f

+commit 4f2dc564851dc04b271a2260c834643dfd86c724

现在你已经安装好了 Rust,让我们来编写你的第一个 Rust 程序。当学习一门新语言的时候,编写一个在屏幕上打印 “Hello, world!” 文本的小程序是一个传统,而在这一部分,我们将遵循这个传统。

@@ -7561,7 +7561,217 @@ 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 中有一个迭代器并调用了其像mapcollect这样的方法。然而在列表 13-6 中,只实现了Counternext方法。Counter如何才能得到像mapcollect这样的方法呢?

+

好吧,当讲到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)。

+

因为实现了Iteratornext方法,所有这些方法调用都是可能的。请查看标准库文档来寻找迭代器可能会用得上的方法。

+

改进 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中字段searchfilename的值,这样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的返回值应该是希望放入Configsearch字段的值。使用match来在next返回Some时提取值,而在因为没有足够的参数(这会造成next调用返回None)而提早返回Err值。

+

filename值也进行相同处理。稍微有些可惜的是searchfilenamematch表达式是如此的相似。如果可以对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,抽象出这些老生常谈的代码将使得我们更容易看清代码所特有的概念,比如迭代器中用于过滤每个元素的条件。

+

不过他们真的完全等同吗?当然更底层的循环会更快一些。让我们聊聊性能吧。

+

性能

+
+

ch13-04-performance.md +
+commit 4f2dc564851dc04b271a2260c834643dfd86c724

+
+

哪一个版本的grep函数会更快一些呢:是直接使用for循环的版本还是使用迭代器的版本呢?我们将运行一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进String并寻找其中的单词 "the"。如下是for循环版本和迭代器版本的 grep 函数的性能测试结果:

+
test bench_grep_for  ... bench:  19,620,300 ns/iter (+/- 915,700)
+test bench_grep_iter ... bench:  19,234,900 ns/iter (+/- 657,200)
+
+

结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,光是这一点并不是为了证明他们是完全等同的,而是提供了一个大体上比较这两种实现的方向。对于真正的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的零成本抽象zero-cost abstractions)之一,它意味着抽象并不会强加运行时开销,它与本贾尼·斯特劳斯特卢普,C++ 的设计和实现者所定义的零开销zero-overhead)如出一辙:

+
+

In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

+
    +
  • Bjarne Stroustrup "Foundations of C++"
  • +
+

从整体来说,C++ 的实现遵循了零开销原则:你不需要的,无需为他们买单。更有甚者的是:你需要的时候,也不可能找到其他更好的代码了。

+
    +
  • 本贾尼·斯特劳斯特卢普 "Foundations of C++"
  • +
+
+

作为另一个例子,这里有一些来自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫buffer的数据 slice、一个 12 个系数列表的coefficients、和一个移位位数的qlp_shift。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简洁的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:

+
let buffer: &mut [i32];
+let coefficients: [i64; 12];
+let qlp_shift: i16;
+
+for i in 12..buffer.len() {
+    let prediction = coefficients.iter()
+                                 .zip(&buffer[i - 12..i])
+                                 .map(|(&c, &s)| c * s as i64)
+                                 .sum::<i64>() >> qlp_shift;
+    let delta = buffer[i];
+    buffer[i] = prediction as i32 + delta;
+}
+
+

为了计算prediction的值,这些代码遍历了coefficients中的 12 个值,使用zip方法将系数与buffer的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移qlp_shift位。

+

像音频解码器这样的程序通常非常看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历coefficients的值完全用不到循环:Rust 知道这里会迭代 12 次,所以它“展开”了循环。所有的系数都被储存在了寄存器中(这意味着访问他们非常快)。也没有数组访问边界检查。这是极端有效率的。

+

现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。

+

总结

+

闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。他们对 Rust 直白的表达高级概念的能力有很大贡献。闭包和迭代器的实现,以及 Rust 的零成本抽象,也使得运行时性能不受影响。

+

现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多cargo的功能,他们是如何将项目准备好分享给世界的。

diff --git a/src/ch01-00-introduction.md b/src/ch01-00-introduction.md index 2101b8d..d51ede2 100644 --- a/src/ch01-00-introduction.md +++ b/src/ch01-00-introduction.md @@ -1,8 +1,8 @@ # 介绍 -> [ch01-00-introduction.md](https://github.com/rust-lang/book/blob/master/src/ch01-00-introduction.md) +> [ch01-00-introduction.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-00-introduction.md) >
-> commit c51c14215d2ee2cb481bc8a942a3769c6d9a2e1a +> commit 4f2dc564851dc04b271a2260c834643dfd86c724 欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的人们。 diff --git a/src/ch01-01-installation.md b/src/ch01-01-installation.md index 460ac53..ad4d774 100644 --- a/src/ch01-01-installation.md +++ b/src/ch01-01-installation.md @@ -1,8 +1,8 @@ ## 安装 -> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/src/ch01-01-installation.md) +> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-01-installation.md) >
-> commit f828919e62aa542aaaae03c1fb565da42374213e +> commit 4f2dc564851dc04b271a2260c834643dfd86c724 使用 Rust 的第一步是安装。你需要联网来执行本章的命令,因为我们要从网上下载 Rust。 diff --git a/src/ch01-02-hello-world.md b/src/ch01-02-hello-world.md index ff3ff2c..4b62a3c 100644 --- a/src/ch01-02-hello-world.md +++ b/src/ch01-02-hello-world.md @@ -1,8 +1,8 @@ ## Hello, World! -> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/master/src/ch01-02-hello-world.md) +> [ch01-02-hello-world.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-02-hello-world.md) >
-> commit ccbeea7b9fe115cd545881618fe14229d18b307f +> commit 4f2dc564851dc04b271a2260c834643dfd86c724 现在你已经安装好了 Rust,让我们来编写你的第一个 Rust 程序。当学习一门新语言的时候,编写一个在屏幕上打印 “Hello, world!” 文本的小程序是一个传统,而在这一部分,我们将遵循这个传统。 diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index 8dc2a9f..5d643b3 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -118,4 +118,78 @@ impl Iterator for Counter { `next`方法是迭代器的主要接口,它返回一个`Option`。如果它是`Some(value)`,相当于可以迭代器中获取另一个值。如果它是`None`,迭代器就结束了。在`next`方法中可以进行任何迭代器需要的计算。在这个例子中,我们对当前状态加一,接着检查其是否仍然小于六。如果是,返回`Some(self.count)`来产生下一个值。如果大于等于六,迭代结束并返回`None`。 -迭代器 trait 指定当其返回`None`,就代表迭代结束。该 trait 并不强制任何在`next`方法返回`None`后再次调用时必须有的行为。在这个情况下,在第一次返回`None`后每一次调用`next`仍然返回`None`,不过其内部`count`字段会依次增长到`u32`的最大值,接着`count`会溢出(在调试模式会`panic!`而在发布模式则会折叠从最小值开始)。 \ No newline at end of file +迭代器 trait 指定当其返回`None`,就代表迭代结束。该 trait 并不强制任何在`next`方法返回`None`后再次调用时必须有的行为。在这个情况下,在第一次返回`None`后每一次调用`next`仍然返回`None`,不过其内部`count`字段会依次增长到`u32`的最大值,接着`count`会溢出(在调试模式会`panic!`而在发布模式则会折叠从最小值开始)。有些其他的迭代器则选择再次从头开始迭代。如果需要确保迭代器在返回第一个`None`之后所有的`next`方法调用都返回`None`,可以使用`fuse`方法来创建不同于任何其他的迭代器。 + +一旦实现了`Iterator` trait,我们就有了一个迭代器!可以通过不停的调用`Counter`结构体的`next`方法来使用迭代器的功能: + +```rust,ignore +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`实例第一个之后的值相组合,将每组数相乘,并只保留能被三整除的相乘结果,最后将保留结果相加,我们可以这么做: + + +```rust +# 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 { +# // 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`方法,所有这些方法调用都是可能的。请查看标准库文档来寻找迭代器可能会用得上的方法。 \ No newline at end of file diff --git a/src/ch13-03-improving-our-io-project.md b/src/ch13-03-improving-our-io-project.md index e69de29..82877d1 100644 --- a/src/ch13-03-improving-our-io-project.md +++ b/src/ch13-03-improving-our-io-project.md @@ -0,0 +1,143 @@ +## 改进 I/O 项目 + +> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-03-improving-our-io-project.md) +>
+> commit 4f2dc564851dc04b271a2260c834643dfd86c724 + +在我们上一章实现的`grep` I/O 项目中,其中有一些地方的代码可以使用迭代器来变得更清楚简洁一些。让我们看看迭代器如何能够改进`Config::new`函数和`grep`函数的实现。 + +### 使用迭代器并去掉`clone` + +回到列表 12-8 中,这些代码获取一个`String` slice 并创建一个`Config`结构体的实例,它检查参数的数量、索引 slice 中的值、并克隆这些值以便`Config`可以拥有他们的所有权: + +```rust,ignore +impl Config { + fn new(args: &[String]) -> Result { + 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: + +```rust,ignore +fn main() { + let config = Config::new(env::args()); + // ...snip... +``` + + + +如果参看标准库中`env::args`函数的文档,我们会发现它的返回值类型是`std::env::Args`。所以下一步就是更新`Config::new`的签名使得参数`args`拥有`std::env::Args`类型而不是`&[String]`: + +```rust,ignore +impl Config { + fn new(args: std::env::Args) -> Result { + // ...snip... +``` + + + +之后我们将修复`Config::new`的函数体。因为标准库文档也表明,`std::env::Args`实现了`Iterator` trait,所以我们知道可以调用其`next`方法!如下就是新的代码: + +```rust +# struct Config { +# search: String, +# filename: String, +# } +# +impl Config { + fn new(mut args: std::env::Args) -> Result { + 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`函数中: + + + +```rust +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,像这样使用迭代器适配器方法来实现: + +```rust +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`函数使用如下同样的技术: + + + +```rust +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,抽象出这些老生常谈的代码将使得我们更容易看清代码所特有的概念,比如迭代器中用于过滤每个元素的条件。 + +不过他们真的完全等同吗?当然更底层的循环会更快一些。让我们聊聊性能吧。 \ No newline at end of file diff --git a/src/ch13-04-performance.md b/src/ch13-04-performance.md index e69de29..5a04dc5 100644 --- a/src/ch13-04-performance.md +++ b/src/ch13-04-performance.md @@ -0,0 +1,51 @@ +## 性能 + +> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-04-performance.md) +>
+> commit 4f2dc564851dc04b271a2260c834643dfd86c724 + +哪一个版本的`grep`函数会更快一些呢:是直接使用`for`循环的版本还是使用迭代器的版本呢?我们将运行一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进`String`并寻找其中的单词 "the"。如下是`for`循环版本和迭代器版本的 grep 函数的性能测试结果: + +``` +test bench_grep_for ... bench: 19,620,300 ns/iter (+/- 915,700) +test bench_grep_iter ... bench: 19,234,900 ns/iter (+/- 657,200) +``` + +结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,光是这一点并不是为了证明他们是完全等同的,而是提供了一个大体上比较这两种实现的方向。对于**真正**的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的**零成本抽象**(*zero-cost abstractions*)之一,它意味着抽象并不会强加运行时开销,它与本贾尼·斯特劳斯特卢普,C++ 的设计和实现者所定义的**零开销**(*zero-overhead*)如出一辙: + +> In general, C++ implementations obey the zero-overhead principle: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better. +> +> - Bjarne Stroustrup "Foundations of C++" +> +> 从整体来说,C++ 的实现遵循了零开销原则:你不需要的,无需为他们买单。更有甚者的是:你需要的时候,也不可能找到其他更好的代码了。 +> +> - 本贾尼·斯特劳斯特卢普 "Foundations of C++" + +作为另一个例子,这里有一些来自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫`buffer`的数据 slice、一个 12 个系数列表的`coefficients`、和一个移位位数的`qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简洁的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码: + +```rust,ignore +let buffer: &mut [i32]; +let coefficients: [i64; 12]; +let qlp_shift: i16; + +for i in 12..buffer.len() { + let prediction = coefficients.iter() + .zip(&buffer[i - 12..i]) + .map(|(&c, &s)| c * s as i64) + .sum::() >> qlp_shift; + let delta = buffer[i]; + buffer[i] = prediction as i32 + delta; +} +``` + +为了计算`prediction`的值,这些代码遍历了`coefficients`中的 12 个值,使用`zip`方法将系数与`buffer`的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移`qlp_shift`位。 + +像音频解码器这样的程序通常非常看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历`coefficients`的值完全用不到循环:Rust 知道这里会迭代 12 次,所以它“展开”了循环。所有的系数都被储存在了寄存器中(这意味着访问他们非常快)。也没有数组访问边界检查。这是极端有效率的。 + +现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。 + +## 总结 + +闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。他们对 Rust 直白的表达高级概念的能力有很大贡献。闭包和迭代器的实现,以及 Rust 的零成本抽象,也使得运行时性能不受影响。 + +现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多`cargo`的功能,他们是如何将项目准备好分享给世界的。 \ No newline at end of file