diff --git a/docs/ch13-00-functional-features.html b/docs/ch13-00-functional-features.html index c9f55a4..61de365 100644 --- a/docs/ch13-00-functional-features.html +++ b/docs/ch13-00-functional-features.html @@ -73,6 +73,15 @@
commit 4f2dc564851dc04b271a2260c834643dfd86c724

+

Rust 的设计灵感来源于很多前人的成果。影响 Rust 的其中之一就是函数式编程,在这里函数也是值并可以被用作参数或其他函数的返回值、赋值给变量等等。我们将回避解释函数式编程的具体是什么以及其优缺点,而是突出展示 Rust 中那些类似被认为是函数式的编程语言中的功能。

+

更具体的,我们将要涉及:

+ +

这并不是一个 Rust 受函数式风格影响的完整功能列表:还有模式匹配、枚举和很多其他功能。不过掌握闭包和迭代器则是编写符合语言风格的快速的 Rust 代码的重要一环。

diff --git a/docs/ch13-01-closures.html b/docs/ch13-01-closures.html index 7f49fc3..a90aced 100644 --- a/docs/ch13-01-closures.html +++ b/docs/ch13-01-closures.html @@ -67,7 +67,131 @@
- +

闭包

+
+

ch13-01-closures.md +
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+
+

Rust 提供了定义闭包的能力,它类似于函数。让我们先不从技术上的定义开始,而是看看闭包语句结构,然后再返回他们的定义。列表 13-1 展示了一个被赋值给变量add_one的小的闭包定义,之后可以用这个变量来调用闭包:

+

Filename: src/main.rs

+
fn main() {
+    let add_one = |x| x + 1;
+
+    let five = add_one(4);
+
+    assert_eq!(5, five);
+}
+
+

Listing 13-1: A closure that takes one parameter and adds +one to it, assigned to the variable add_one

+

闭包的定义位于第一行,展示了闭包获取了一个叫做x的参数。闭包的参数位于竖线之间(|)。

+

这是一个很小的闭包,它只包含一个表达式。列表 13-2 展示了一个稍微复杂一点的闭包:

+

Filename: src/main.rs

+
fn main() {
+    let calculate = |a, b| {
+        let mut result = a * 2;
+
+        result += b;
+
+        result
+    };
+
+    assert_eq!(7, calculate(2, 3)); // 2 * 2 + 3 == 7
+    assert_eq!(13, calculate(4, 5)); // 4 * 2 + 5 == 13
+}
+
+

Listing 13-2: A closure with two parameters and multiple +expressions in its body

+

可以通过大括号来定义多于一个表达式的闭包体。

+

你会注意到一些闭包不同于fn关键字定义的函数的地方。第一个不同是并不需要声明闭包的参数和返回值的类型。也可以选择加上类型注解;列表 13-3 展示了列表 13-1 中闭包带有参数和返回值类型注解的版本:

+

Filename: src/main.rs

+
fn main() {
+    let add_one = |x: i32| -> i32 { x + 1 };
+
+    assert_eq!(2, add_one(1));
+}
+
+

Listing 13-3: A closure definition with optional +parameter and return value type annotations

+

在带有类型注解的情况下闭包的语法于函数就更接近了。让我们来更直接的比较一下不同闭包的语法与函数的语法。这里增加了一些空格来对齐相关的部分:

+
fn  add_one_v1   (x: i32) -> i32 { x + 1 }  // a function
+let add_one_v2 = |x: i32| -> i32 { x + 1 }; // the full syntax for a closure
+let add_one_v3 = |x|             { x + 1 }; // a closure eliding types
+let add_one_v4 = |x|               x + 1  ; // without braces
+
+

定义闭包时并要求类型注解而在定义函数是要求的原因在于函数是显式暴露给用户的接口的一部分,所以为了严格的定义接口确保所有人都同意函数使用和返回的值类型是很重要的。但是闭包并不像函数那样用于暴露接口:他们存在于绑定中并直接被调用。强制标注类型就等于为了很小的优点而显著的降低了工程性(本末倒置)。

+

不过闭包的定义确实会推断每一个参数和返回值的类型。例如,如果用i8调用列表 13-1 中没有类型注解的闭包,如果接着用i32调用同一闭包则会得到一个错误:

+

Filename: src/main.rs

+
let add_one = |x| x + 1;
+
+let five = add_one(4i8);
+assert_eq!(5i8, five);
+
+let three = add_one(2i32);
+
+

编译器给出如下错误:

+
error[E0308]: mismatched types
+ -->
+  |
+7 | let three = add_one(2i32);
+  |                     ^^^^ expected i8, found i32
+
+

因为闭包是直接被调用的所以能可靠的推断出其类型,再强制要求标注类型就显得有些冗余了。

+

闭包与函数语法不同还有另一个原因是,它与函数有着不同的行为:闭包拥有其环境(上下文)

+

闭包可以引用其环境

+

我们知道函数只能使用其作用域内的变量,或者要么是const的要么是被声明为参数的。闭包则可以做的更多:闭包允许使用包含他们的作用域的变量。列表 13-4 是一个在equal_to_x变量中并使用其周围环境中变量x的闭包的例子:

+

Filename: src/main.rs

+
fn main() {
+    let x = 4;
+
+    let equal_to_x = |z| z == x;
+
+    let y = 4;
+
+    assert!(equal_to_x(y));
+}
+
+

Listing 13-4: Example of a closure that refers to a +variable in its enclosing scope

+

这里。即便x并不是equal_to_x的一个参数,equal_to_x闭包也被允许使用它,因为变量x定义于同样定义equal_to_x的作用域中。并不允许在函数中进行与列表 13-4 相同的操作;尝试这么做看看会发生什么:

+

Filename: src/main.rs

+
fn main() {
+    let x = 4;
+
+    fn equal_to_x(z: i32) -> bool { z == x }
+
+    let y = 4;
+
+    assert!(equal_to_x(y));
+}
+
+

我们会得到一个错误:

+
error[E0434]: can't capture dynamic environment in a fn item; use the || { ... }
+closure form instead
+ -->
+  |
+4 |     fn equal_to_x(z: i32) -> bool { z == x }
+  |                                          ^
+
+

编译器甚至提醒我们这只能用于闭包!

+

获取他们环境中值的闭包主要用于开始新线程的场景。我们也可以定义以闭包作为参数的函数,通过使用Fn trait。这里是一个函数call_with_one的例子,它的签名有一个闭包参数:

+
fn call_with_one<F>(some_closure: F) -> i32
+    where F: Fn(i32) -> i32 {
+
+    some_closure(1)
+}
+
+let answer = call_with_one(|x| x + 2);
+
+assert_eq!(3, answer);
+
+

我们将|x| x + 2传递给了call_with_one,而call_with_one1作为参数调用了这个闭包。some_closure调用的返回值接着被call_with_one返回。

+

call_with_one的签名使用了第十章 trait 部分讨论到的where语法。some_closure参数有一个泛型类型F,它在where从句中被定义为拥有Fn(i32) -> i32 trait bound。Fn trait 代表了一个闭包,而且可以给Fn trait 增加类型来代表一个特定类型的闭包。在这种情况下,闭包拥有一个i32的参数并返回一个i32,所以泛型的 trait bound 被指定为Fn(i32) -> i32

+

在函数签名中指定闭包要求使用泛型和 trait bound。每一个闭包都有一个独特的类型,所以不能写出闭包的类型而必须使用泛型。

+

Fn并不是唯一可以指定闭包的 trait bound,事实上有三个:FnFnMutFnOnce。这是在 Rust 中经常见到的三种模式的延续:借用、可变借用和获取所有权。用Fn来指定可能只会借用其环境中值的闭包。用FnMut来指定会修改环境中值的闭包,而如果闭包会获取环境值的所有权则使用FnOnce。大部分情况可以从Fn开始,而编译器会根据调用闭包时会发生什么来告诉你是否需要FnMutFnOnce

+

为了展示拥有闭包作为参数的函数的应用场景,让我们继续下一主题:迭代器。

+
diff --git a/docs/ch13-02-iterators.html b/docs/ch13-02-iterators.html index 1d07058..983e24f 100644 --- a/docs/ch13-02-iterators.html +++ b/docs/ch13-02-iterators.html @@ -67,7 +67,95 @@
-

迭代器

+

迭代器

+
+

ch13-02-iterators.md +
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+
+

迭代器是 Rust 中的一个模式,它允许你对一个项的序列进行某些处理。例如。列表 13-5 中对 vecctor 中的每一个数加一:

+
let v1 = vec![1, 2, 3];
+
+let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect();
+
+assert_eq!(v2, [2, 3, 4]);
+
+

Listing 13-5: Using an iterator, map, and collect to +add one to each number in a vector

+ +

vector 的iter方法允许从 vector 创建一个迭代器iterator)。接着迭代器上的map方法调用允许我们处理每一个元素:在这里,我们向map传递了一个对每一个元素x加一的闭包。map是最基本的与比较交互的方法之一,因为依次处理每一个元素是非常有用的!最后collect方法消费了迭代器并将其元素存放到一个新的数据结构中。在这个例子中,因为我们指定v2的类型是Vec<i32>collect将会创建一个i32的 vector。

+

map这样的迭代器方法有时被称为迭代器适配器iterator adaptors),因为他们获取一个迭代器并产生一个新的迭代器。也就是说,map在之前迭代器的基础上通过调用传递给它的闭包来创建了一个新的值序列的迭代器。

+

概括一下,这行代码进行了如下工作:

+
    +
  1. 从 vector 中创建了一个迭代器。
  2. +
  3. 使用map适配器和一个闭包参数对每一个元素加一。
  4. +
  5. 使用collect适配器来消费迭代去并生成了一个新的 vector。
  6. +
+

这就是如何产生结果[2, 3, 4]的。如你所见,闭包是使用迭代器的很重要的一部分:他们提供了一个自定义类似map这样的迭代器适配器的行为的方法。

+

迭代器是惰性的

+

在上一部分,你可能已经注意到了一个微妙的用词区别:我们说map适配adapts)了一个迭代器,而collect消费consumes)了一个迭代器。这是有意为之的。单独的迭代器并不会做任何工作;他们是惰性的。也就是说,像列表 13-5 的代码但是不调用collect的话:

+
let v1: Vec<i32> = vec![1, 2, 3];
+
+v1.iter().map(|x| x + 1); // without collect
+
+

这可以编译,不过会给出一个警告:

+
warning: unused result which must be used: iterator adaptors are lazy and do
+nothing unless consumed, #[warn(unused_must_use)] on by default
+ --> src/main.rs:4:1
+  |
+4 | v1.iter().map(|x| x + 1); // without collect
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

这个警告是因为迭代器适配器实际上并不自己进行处理。他们需要一些其他方法来触发迭代器链的计算。我们称之为消费迭代器consuming adaptors),而collect就是其中之一。

+

那么如何知道迭代器方法是否消费了迭代器呢?还有哪些适配器是可用的呢?为此,让我们看看Iterator trait。

+

Iterator trait

+

迭代器都实现了一个标准库中叫做Iterator的 trait。其定义看起来像这样:

+
trait Iterator {
+    type Item;
+
+    fn next(&mut self) -> Option<Self::Item>;
+}
+
+

这里有一些还未讲到的新语法:type ItemSelf::Item定义了这个 trait 的关联类型associated type),第XX章会讲到关联类型。现在所有你需要知道就是这些代码表示Iterator trait 要求你也定义一个Item类型,而这个Item类型用作next方法的返回值。换句话说,Item类型将是迭代器返回的元素的类型。

+

让我们使用Iterator trait 来创建一个从一数到五的迭代器Counter。首先,需要创建一个结构体来存放迭代器的当前状态,它有一个u32的字段count。我们也定义了一个new方法,当然这并不是必须的。因为我们希望Counter能从一数到五,所以它总是从零开始:

+
struct Counter {
+    count: u32,
+}
+
+impl Counter {
+    fn new() -> Counter {
+        Counter { count: 0 }
+    }
+}
+
+

接下来,我们将通过定义next方法来为Counter类型实现Iterator trait。我们希望迭代器的工作方式是对当前状态加一(这就是为什么将count初始化为零,这样迭代器首先就会返回一)。如果count仍然小于六,将返回当前状态,不过如果count大于等于六,迭代器将返回None,如列表 13-6 所示:

+
# struct Counter {
+#     count: u32,
+# }
+#
+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
+        }
+    }
+}
+
+

Listing 13-6: Implementing the Iterator trait on our +Counter struct

+ +

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!而在发布模式则会折叠从最小值开始)。

diff --git a/docs/print.html b/docs/print.html index d39a55b..0db39a8 100644 --- a/docs/print.html +++ b/docs/print.html @@ -7340,7 +7340,228 @@ How dreary to be somebody!
commit 4f2dc564851dc04b271a2260c834643dfd86c724

-

迭代器

+

Rust 的设计灵感来源于很多前人的成果。影响 Rust 的其中之一就是函数式编程,在这里函数也是值并可以被用作参数或其他函数的返回值、赋值给变量等等。我们将回避解释函数式编程的具体是什么以及其优缺点,而是突出展示 Rust 中那些类似被认为是函数式的编程语言中的功能。

+

更具体的,我们将要涉及:

+ +

这并不是一个 Rust 受函数式风格影响的完整功能列表:还有模式匹配、枚举和很多其他功能。不过掌握闭包和迭代器则是编写符合语言风格的快速的 Rust 代码的重要一环。

+

闭包

+
+

ch13-01-closures.md +
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+
+

Rust 提供了定义闭包的能力,它类似于函数。让我们先不从技术上的定义开始,而是看看闭包语句结构,然后再返回他们的定义。列表 13-1 展示了一个被赋值给变量add_one的小的闭包定义,之后可以用这个变量来调用闭包:

+

Filename: src/main.rs

+
fn main() {
+    let add_one = |x| x + 1;
+
+    let five = add_one(4);
+
+    assert_eq!(5, five);
+}
+
+

Listing 13-1: A closure that takes one parameter and adds +one to it, assigned to the variable add_one

+

闭包的定义位于第一行,展示了闭包获取了一个叫做x的参数。闭包的参数位于竖线之间(|)。

+

这是一个很小的闭包,它只包含一个表达式。列表 13-2 展示了一个稍微复杂一点的闭包:

+

Filename: src/main.rs

+
fn main() {
+    let calculate = |a, b| {
+        let mut result = a * 2;
+
+        result += b;
+
+        result
+    };
+
+    assert_eq!(7, calculate(2, 3)); // 2 * 2 + 3 == 7
+    assert_eq!(13, calculate(4, 5)); // 4 * 2 + 5 == 13
+}
+
+

Listing 13-2: A closure with two parameters and multiple +expressions in its body

+

可以通过大括号来定义多于一个表达式的闭包体。

+

你会注意到一些闭包不同于fn关键字定义的函数的地方。第一个不同是并不需要声明闭包的参数和返回值的类型。也可以选择加上类型注解;列表 13-3 展示了列表 13-1 中闭包带有参数和返回值类型注解的版本:

+

Filename: src/main.rs

+
fn main() {
+    let add_one = |x: i32| -> i32 { x + 1 };
+
+    assert_eq!(2, add_one(1));
+}
+
+

Listing 13-3: A closure definition with optional +parameter and return value type annotations

+

在带有类型注解的情况下闭包的语法于函数就更接近了。让我们来更直接的比较一下不同闭包的语法与函数的语法。这里增加了一些空格来对齐相关的部分:

+
fn  add_one_v1   (x: i32) -> i32 { x + 1 }  // a function
+let add_one_v2 = |x: i32| -> i32 { x + 1 }; // the full syntax for a closure
+let add_one_v3 = |x|             { x + 1 }; // a closure eliding types
+let add_one_v4 = |x|               x + 1  ; // without braces
+
+

定义闭包时并要求类型注解而在定义函数是要求的原因在于函数是显式暴露给用户的接口的一部分,所以为了严格的定义接口确保所有人都同意函数使用和返回的值类型是很重要的。但是闭包并不像函数那样用于暴露接口:他们存在于绑定中并直接被调用。强制标注类型就等于为了很小的优点而显著的降低了工程性(本末倒置)。

+

不过闭包的定义确实会推断每一个参数和返回值的类型。例如,如果用i8调用列表 13-1 中没有类型注解的闭包,如果接着用i32调用同一闭包则会得到一个错误:

+

Filename: src/main.rs

+
let add_one = |x| x + 1;
+
+let five = add_one(4i8);
+assert_eq!(5i8, five);
+
+let three = add_one(2i32);
+
+

编译器给出如下错误:

+
error[E0308]: mismatched types
+ -->
+  |
+7 | let three = add_one(2i32);
+  |                     ^^^^ expected i8, found i32
+
+

因为闭包是直接被调用的所以能可靠的推断出其类型,再强制要求标注类型就显得有些冗余了。

+

闭包与函数语法不同还有另一个原因是,它与函数有着不同的行为:闭包拥有其环境(上下文)

+

闭包可以引用其环境

+

我们知道函数只能使用其作用域内的变量,或者要么是const的要么是被声明为参数的。闭包则可以做的更多:闭包允许使用包含他们的作用域的变量。列表 13-4 是一个在equal_to_x变量中并使用其周围环境中变量x的闭包的例子:

+

Filename: src/main.rs

+
fn main() {
+    let x = 4;
+
+    let equal_to_x = |z| z == x;
+
+    let y = 4;
+
+    assert!(equal_to_x(y));
+}
+
+

Listing 13-4: Example of a closure that refers to a +variable in its enclosing scope

+

这里。即便x并不是equal_to_x的一个参数,equal_to_x闭包也被允许使用它,因为变量x定义于同样定义equal_to_x的作用域中。并不允许在函数中进行与列表 13-4 相同的操作;尝试这么做看看会发生什么:

+

Filename: src/main.rs

+
fn main() {
+    let x = 4;
+
+    fn equal_to_x(z: i32) -> bool { z == x }
+
+    let y = 4;
+
+    assert!(equal_to_x(y));
+}
+
+

我们会得到一个错误:

+
error[E0434]: can't capture dynamic environment in a fn item; use the || { ... }
+closure form instead
+ -->
+  |
+4 |     fn equal_to_x(z: i32) -> bool { z == x }
+  |                                          ^
+
+

编译器甚至提醒我们这只能用于闭包!

+

获取他们环境中值的闭包主要用于开始新线程的场景。我们也可以定义以闭包作为参数的函数,通过使用Fn trait。这里是一个函数call_with_one的例子,它的签名有一个闭包参数:

+
fn call_with_one<F>(some_closure: F) -> i32
+    where F: Fn(i32) -> i32 {
+
+    some_closure(1)
+}
+
+let answer = call_with_one(|x| x + 2);
+
+assert_eq!(3, answer);
+
+

我们将|x| x + 2传递给了call_with_one,而call_with_one1作为参数调用了这个闭包。some_closure调用的返回值接着被call_with_one返回。

+

call_with_one的签名使用了第十章 trait 部分讨论到的where语法。some_closure参数有一个泛型类型F,它在where从句中被定义为拥有Fn(i32) -> i32 trait bound。Fn trait 代表了一个闭包,而且可以给Fn trait 增加类型来代表一个特定类型的闭包。在这种情况下,闭包拥有一个i32的参数并返回一个i32,所以泛型的 trait bound 被指定为Fn(i32) -> i32

+

在函数签名中指定闭包要求使用泛型和 trait bound。每一个闭包都有一个独特的类型,所以不能写出闭包的类型而必须使用泛型。

+

Fn并不是唯一可以指定闭包的 trait bound,事实上有三个:FnFnMutFnOnce。这是在 Rust 中经常见到的三种模式的延续:借用、可变借用和获取所有权。用Fn来指定可能只会借用其环境中值的闭包。用FnMut来指定会修改环境中值的闭包,而如果闭包会获取环境值的所有权则使用FnOnce。大部分情况可以从Fn开始,而编译器会根据调用闭包时会发生什么来告诉你是否需要FnMutFnOnce

+

为了展示拥有闭包作为参数的函数的应用场景,让我们继续下一主题:迭代器。

+

迭代器

+
+

ch13-02-iterators.md +
+commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56

+
+

迭代器是 Rust 中的一个模式,它允许你对一个项的序列进行某些处理。例如。列表 13-5 中对 vecctor 中的每一个数加一:

+
let v1 = vec![1, 2, 3];
+
+let v2: Vec<i32> = v1.iter().map(|x| x + 1).collect();
+
+assert_eq!(v2, [2, 3, 4]);
+
+

Listing 13-5: Using an iterator, map, and collect to +add one to each number in a vector

+ +

vector 的iter方法允许从 vector 创建一个迭代器iterator)。接着迭代器上的map方法调用允许我们处理每一个元素:在这里,我们向map传递了一个对每一个元素x加一的闭包。map是最基本的与比较交互的方法之一,因为依次处理每一个元素是非常有用的!最后collect方法消费了迭代器并将其元素存放到一个新的数据结构中。在这个例子中,因为我们指定v2的类型是Vec<i32>collect将会创建一个i32的 vector。

+

map这样的迭代器方法有时被称为迭代器适配器iterator adaptors),因为他们获取一个迭代器并产生一个新的迭代器。也就是说,map在之前迭代器的基础上通过调用传递给它的闭包来创建了一个新的值序列的迭代器。

+

概括一下,这行代码进行了如下工作:

+
    +
  1. 从 vector 中创建了一个迭代器。
  2. +
  3. 使用map适配器和一个闭包参数对每一个元素加一。
  4. +
  5. 使用collect适配器来消费迭代去并生成了一个新的 vector。
  6. +
+

这就是如何产生结果[2, 3, 4]的。如你所见,闭包是使用迭代器的很重要的一部分:他们提供了一个自定义类似map这样的迭代器适配器的行为的方法。

+

迭代器是惰性的

+

在上一部分,你可能已经注意到了一个微妙的用词区别:我们说map适配adapts)了一个迭代器,而collect消费consumes)了一个迭代器。这是有意为之的。单独的迭代器并不会做任何工作;他们是惰性的。也就是说,像列表 13-5 的代码但是不调用collect的话:

+
let v1: Vec<i32> = vec![1, 2, 3];
+
+v1.iter().map(|x| x + 1); // without collect
+
+

这可以编译,不过会给出一个警告:

+
warning: unused result which must be used: iterator adaptors are lazy and do
+nothing unless consumed, #[warn(unused_must_use)] on by default
+ --> src/main.rs:4:1
+  |
+4 | v1.iter().map(|x| x + 1); // without collect
+  | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+

这个警告是因为迭代器适配器实际上并不自己进行处理。他们需要一些其他方法来触发迭代器链的计算。我们称之为消费迭代器consuming adaptors),而collect就是其中之一。

+

那么如何知道迭代器方法是否消费了迭代器呢?还有哪些适配器是可用的呢?为此,让我们看看Iterator trait。

+

Iterator trait

+

迭代器都实现了一个标准库中叫做Iterator的 trait。其定义看起来像这样:

+
trait Iterator {
+    type Item;
+
+    fn next(&mut self) -> Option<Self::Item>;
+}
+
+

这里有一些还未讲到的新语法:type ItemSelf::Item定义了这个 trait 的关联类型associated type),第XX章会讲到关联类型。现在所有你需要知道就是这些代码表示Iterator trait 要求你也定义一个Item类型,而这个Item类型用作next方法的返回值。换句话说,Item类型将是迭代器返回的元素的类型。

+

让我们使用Iterator trait 来创建一个从一数到五的迭代器Counter。首先,需要创建一个结构体来存放迭代器的当前状态,它有一个u32的字段count。我们也定义了一个new方法,当然这并不是必须的。因为我们希望Counter能从一数到五,所以它总是从零开始:

+
struct Counter {
+    count: u32,
+}
+
+impl Counter {
+    fn new() -> Counter {
+        Counter { count: 0 }
+    }
+}
+
+

接下来,我们将通过定义next方法来为Counter类型实现Iterator trait。我们希望迭代器的工作方式是对当前状态加一(这就是为什么将count初始化为零,这样迭代器首先就会返回一)。如果count仍然小于六,将返回当前状态,不过如果count大于等于六,迭代器将返回None,如列表 13-6 所示:

+
# struct Counter {
+#     count: u32,
+# }
+#
+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
+        }
+    }
+}
+
+

Listing 13-6: Implementing the Iterator trait on our +Counter struct

+ +

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!而在发布模式则会折叠从最小值开始)。

diff --git a/src/ch13-00-functional-features.md b/src/ch13-00-functional-features.md index 93f7ff1..9bc13b5 100644 --- a/src/ch13-00-functional-features.md +++ b/src/ch13-00-functional-features.md @@ -4,3 +4,13 @@ >
> commit 4f2dc564851dc04b271a2260c834643dfd86c724 +Rust 的设计灵感来源于很多前人的成果。影响 Rust 的其中之一就是函数式编程,在这里函数也是值并可以被用作参数或其他函数的返回值、赋值给变量等等。我们将回避解释函数式编程的具体是什么以及其优缺点,而是突出展示 Rust 中那些类似被认为是函数式的编程语言中的功能。 + +更具体的,我们将要涉及: + +* **闭包**(*Closures*),一个可以储存在变量里的类似函数的结构 +* **迭代器**(*Iterators*),一种处理元素序列的方式。。 +* 如何使用这些功能来改进上一章的项目 +* 这些功能的性能。**剧透高能:**他们的速度超乎想象! + +这并不是一个 Rust 受函数式风格影响的完整功能列表:还有模式匹配、枚举和很多其他功能。不过掌握闭包和迭代器则是编写符合语言风格的快速的 Rust 代码的重要一环。 \ No newline at end of file diff --git a/src/ch13-01-closures.md b/src/ch13-01-closures.md index e69de29..de35641 100644 --- a/src/ch13-01-closures.md +++ b/src/ch13-01-closures.md @@ -0,0 +1,177 @@ +## 闭包 + +> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-01-closures.md) +>
+> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 + +Rust 提供了定义**闭包**的能力,它类似于函数。让我们先不从技术上的定义开始,而是看看闭包语句结构,然后再返回他们的定义。列表 13-1 展示了一个被赋值给变量`add_one`的小的闭包定义,之后可以用这个变量来调用闭包: + +Filename: src/main.rs + +```rust +fn main() { + let add_one = |x| x + 1; + + let five = add_one(4); + + assert_eq!(5, five); +} +``` + +Listing 13-1: A closure that takes one parameter and adds +one to it, assigned to the variable `add_one` + +闭包的定义位于第一行,展示了闭包获取了一个叫做`x`的参数。闭包的参数位于竖线之间(`|`)。 + +这是一个很小的闭包,它只包含一个表达式。列表 13-2 展示了一个稍微复杂一点的闭包: + +Filename: src/main.rs + +```rust +fn main() { + let calculate = |a, b| { + let mut result = a * 2; + + result += b; + + result + }; + + assert_eq!(7, calculate(2, 3)); // 2 * 2 + 3 == 7 + assert_eq!(13, calculate(4, 5)); // 4 * 2 + 5 == 13 +} +``` + +Listing 13-2: A closure with two parameters and multiple +expressions in its body + +可以通过大括号来定义多于一个表达式的闭包体。 + +你会注意到一些闭包不同于`fn`关键字定义的函数的地方。第一个不同是并不需要声明闭包的参数和返回值的类型。也可以选择加上类型注解;列表 13-3 展示了列表 13-1 中闭包带有参数和返回值类型注解的版本: + + +Filename: src/main.rs + +```rust +fn main() { + let add_one = |x: i32| -> i32 { x + 1 }; + + assert_eq!(2, add_one(1)); +} +``` + +Listing 13-3: A closure definition with optional +parameter and return value type annotations + +在带有类型注解的情况下闭包的语法于函数就更接近了。让我们来更直接的比较一下不同闭包的语法与函数的语法。这里增加了一些空格来对齐相关的部分: + +```rust,ignore +fn add_one_v1 (x: i32) -> i32 { x + 1 } // a function +let add_one_v2 = |x: i32| -> i32 { x + 1 }; // the full syntax for a closure +let add_one_v3 = |x| { x + 1 }; // a closure eliding types +let add_one_v4 = |x| x + 1 ; // without braces +``` + +定义闭包时并要求类型注解而在定义函数是要求的原因在于函数是显式暴露给用户的接口的一部分,所以为了严格的定义接口确保所有人都同意函数使用和返回的值类型是很重要的。但是闭包并不像函数那样用于暴露接口:他们存在于绑定中并直接被调用。强制标注类型就等于为了很小的优点而显著的降低了工程性(本末倒置)。 + +不过闭包的定义确实会推断每一个参数和返回值的类型。例如,如果用`i8`调用列表 13-1 中没有类型注解的闭包,如果接着用`i32`调用同一闭包则会得到一个错误: + +Filename: src/main.rs + +```rust,ignore +let add_one = |x| x + 1; + +let five = add_one(4i8); +assert_eq!(5i8, five); + +let three = add_one(2i32); +``` + +编译器给出如下错误: + +``` +error[E0308]: mismatched types + --> + | +7 | let three = add_one(2i32); + | ^^^^ expected i8, found i32 +``` + +因为闭包是直接被调用的所以能可靠的推断出其类型,再强制要求标注类型就显得有些冗余了。 + +闭包与函数语法不同还有另一个原因是,它与函数有着不同的行为:闭包拥有其**环境(上下文)**。 + +### 闭包可以引用其环境 + +我们知道函数只能使用其作用域内的变量,或者要么是`const`的要么是被声明为参数的。闭包则可以做的更多:闭包允许使用包含他们的作用域的变量。列表 13-4 是一个在`equal_to_x`变量中并使用其周围环境中变量`x`的闭包的例子: + + +Filename: src/main.rs + +```rust +fn main() { + let x = 4; + + let equal_to_x = |z| z == x; + + let y = 4; + + assert!(equal_to_x(y)); +} +``` + +Listing 13-4: Example of a closure that refers to a +variable in its enclosing scope + +这里。即便`x`并不是`equal_to_x`的一个参数,`equal_to_x`闭包也被允许使用它,因为变量`x`定义于同样定义`equal_to_x`的作用域中。并不允许在函数中进行与列表 13-4 相同的操作;尝试这么做看看会发生什么: + +Filename: src/main.rs + +```rust,ignore +fn main() { + let x = 4; + + fn equal_to_x(z: i32) -> bool { z == x } + + let y = 4; + + assert!(equal_to_x(y)); +} +``` + +我们会得到一个错误: + +``` +error[E0434]: can't capture dynamic environment in a fn item; use the || { ... } +closure form instead + --> + | +4 | fn equal_to_x(z: i32) -> bool { z == x } + | ^ +``` + +编译器甚至提醒我们这只能用于闭包! + +获取他们环境中值的闭包主要用于开始新线程的场景。我们也可以定义以闭包作为参数的函数,通过使用`Fn` trait。这里是一个函数`call_with_one`的例子,它的签名有一个闭包参数: + +```rust +fn call_with_one(some_closure: F) -> i32 + where F: Fn(i32) -> i32 { + + some_closure(1) +} + +let answer = call_with_one(|x| x + 2); + +assert_eq!(3, answer); +``` + +我们将`|x| x + 2`传递给了`call_with_one`,而`call_with_one`用`1`作为参数调用了这个闭包。`some_closure`调用的返回值接着被`call_with_one`返回。 + +`call_with_one`的签名使用了第十章 trait 部分讨论到的`where`语法。`some_closure`参数有一个泛型类型`F`,它在`where`从句中被定义为拥有`Fn(i32) -> i32` trait bound。`Fn` trait 代表了一个闭包,而且可以给`Fn` trait 增加类型来代表一个特定类型的闭包。在这种情况下,闭包拥有一个`i32`的参数并返回一个`i32`,所以泛型的 trait bound 被指定为`Fn(i32) -> i32`。 + +在函数签名中指定闭包要求使用泛型和 trait bound。每一个闭包都有一个独特的类型,所以不能写出闭包的类型而必须使用泛型。 + +`Fn`并不是唯一可以指定闭包的 trait bound,事实上有三个:`Fn`、`FnMut`和`FnOnce`。这是在 Rust 中经常见到的三种模式的延续:借用、可变借用和获取所有权。用`Fn`来指定可能只会借用其环境中值的闭包。用`FnMut`来指定会修改环境中值的闭包,而如果闭包会获取环境值的所有权则使用`FnOnce`。大部分情况可以从`Fn`开始,而编译器会根据调用闭包时会发生什么来告诉你是否需要`FnMut`或`FnOnce`。 + +为了展示拥有闭包作为参数的函数的应用场景,让我们继续下一主题:迭代器。 \ No newline at end of file diff --git a/src/ch13-02-iterators.md b/src/ch13-02-iterators.md index e7442d9..8dc2a9f 100644 --- a/src/ch13-02-iterators.md +++ b/src/ch13-02-iterators.md @@ -1 +1,121 @@ -# 迭代器 +## 迭代器 + +> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md) +>
+> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56 + +迭代器是 Rust 中的一个模式,它允许你对一个项的序列进行某些处理。例如。列表 13-5 中对 vecctor 中的每一个数加一: + +```rust +let v1 = vec![1, 2, 3]; + +let v2: Vec = v1.iter().map(|x| x + 1).collect(); + +assert_eq!(v2, [2, 3, 4]); +``` + +Listing 13-5: Using an iterator, `map`, and `collect` to +add one to each number in a vector + + + +vector 的`iter`方法允许从 vector 创建一个**迭代器**(*iterator*)。接着迭代器上的`map`方法调用允许我们处理每一个元素:在这里,我们向`map`传递了一个对每一个元素`x`加一的闭包。`map`是最基本的与比较交互的方法之一,因为依次处理每一个元素是非常有用的!最后`collect`方法消费了迭代器并将其元素存放到一个新的数据结构中。在这个例子中,因为我们指定`v2`的类型是`Vec`,`collect`将会创建一个`i32`的 vector。 + +像`map`这样的迭代器方法有时被称为**迭代器适配器**(*iterator adaptors*),因为他们获取一个迭代器并产生一个新的迭代器。也就是说,`map`在之前迭代器的基础上通过调用传递给它的闭包来创建了一个新的值序列的迭代器。 + +概括一下,这行代码进行了如下工作: + +1. 从 vector 中创建了一个迭代器。 +2. 使用`map`适配器和一个闭包参数对每一个元素加一。 +3. 使用`collect`适配器来消费迭代去并生成了一个新的 vector。 + +这就是如何产生结果`[2, 3, 4]`的。如你所见,闭包是使用迭代器的很重要的一部分:他们提供了一个自定义类似`map`这样的迭代器适配器的行为的方法。 + +### 迭代器是惰性的 + +在上一部分,你可能已经注意到了一个微妙的用词区别:我们说`map`**适配**(*adapts*)了一个迭代器,而`collect`**消费**(*consumes*)了一个迭代器。这是有意为之的。单独的迭代器并不会做任何工作;他们是惰性的。也就是说,像列表 13-5 的代码但是不调用`collect`的话: + +```rust +let v1: Vec = vec![1, 2, 3]; + +v1.iter().map(|x| x + 1); // without collect +``` + +这可以编译,不过会给出一个警告: + +``` +warning: unused result which must be used: iterator adaptors are lazy and do +nothing unless consumed, #[warn(unused_must_use)] on by default + --> src/main.rs:4:1 + | +4 | v1.iter().map(|x| x + 1); // without collect + | ^^^^^^^^^^^^^^^^^^^^^^^^^ +``` + +这个警告是因为迭代器适配器实际上并不自己进行处理。他们需要一些其他方法来触发迭代器链的计算。我们称之为**消费迭代器**(*consuming adaptors*),而`collect`就是其中之一。 + +那么如何知道迭代器方法是否消费了迭代器呢?还有哪些适配器是可用的呢?为此,让我们看看`Iterator` trait。 + +### `Iterator` trait + +迭代器都实现了一个标准库中叫做`Iterator`的 trait。其定义看起来像这样: + +```rust +trait Iterator { + type Item; + + fn next(&mut self) -> Option; +} +``` + +这里有一些还未讲到的新语法:`type Item`和`Self::Item`定义了这个 trait 的**关联类型**(*associated type*),第XX章会讲到关联类型。现在所有你需要知道就是这些代码表示`Iterator` trait 要求你也定义一个`Item`类型,而这个`Item`类型用作`next`方法的返回值。换句话说,`Item`类型将是迭代器返回的元素的类型。 + +让我们使用`Iterator` trait 来创建一个从一数到五的迭代器`Counter`。首先,需要创建一个结构体来存放迭代器的当前状态,它有一个`u32`的字段`count`。我们也定义了一个`new`方法,当然这并不是必须的。因为我们希望`Counter`能从一数到五,所以它总是从零开始: + +```rust +struct Counter { + count: u32, +} + +impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } +} +``` + +接下来,我们将通过定义`next`方法来为`Counter`类型实现`Iterator` trait。我们希望迭代器的工作方式是对当前状态加一(这就是为什么将`count`初始化为零,这样迭代器首先就会返回一)。如果`count`仍然小于六,将返回当前状态,不过如果`count`大于等于六,迭代器将返回`None`,如列表 13-6 所示: + +```rust +# struct Counter { +# count: u32, +# } +# +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 + } + } +} +``` + +Listing 13-6: Implementing the `Iterator` trait on our +`Counter` struct + + + +`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!`而在发布模式则会折叠从最小值开始)。 \ No newline at end of file