Update ch13-01-closures.md

This commit is contained in:
Gary Wang 2018-08-04 16:25:41 +08:00
parent eb06178a4e
commit c21e3e48e5

View File

@ -2,7 +2,7 @@
> [ch13-01-closures.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-01-closures.md)
> <br>
> commit f23a91d6a2f37ba6d415d2c8ca4302bf1b3a4e9e
> commit bdcba470e4a71d16242b1727bbf99b300c194c46
Rust 的 **闭包***closures*)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。不同于函数,闭包允许捕获调用者作用域中的值。我们将展示闭包的这些功能如何复用代码和自定义行为。
@ -31,7 +31,7 @@ fn simulated_expensive_calculation(intensity: u32) -> u32 {
接下来,`main` 函数中将会包含本例的健身 app 中的重要部分。这代表当用户请求健身计划时 app 会调用的代码。因为与 app 前端的交互与闭包的使用并不相关,所以我们将硬编码代表程序输入的值并打印输出。
所需的输入有:
所需的输入有这些
* **一个来自用户的 intensity 数字**,请求健身计划时指定,它代表用户喜好低强度还是高强度健身。
* **一个随机数**,其会在健身计划中生成变化。
@ -106,11 +106,11 @@ fn generate_workout(intensity: u32, random_number: u32) {
如果用户需要高强度锻炼,这里有一些额外的逻辑:如果 app 生成的随机数刚好是 3app 相反会建议用户稍做休息并补充水分。如果不是,则用户会从复杂算法中得到数分钟跑步的高强度锻炼计划。
数据科学部门的同学告知我们将来会对调用算法的方式做出一些改变。为了在要做这些改动的时候简化更新步骤,我们将重构代码来让它只调用 `simulated_expensive_calculation` 一次。同时还希望去掉目前多余的连续两次函数调用,并不希望在计算过程中增加任何其他此函数的调用。也就是说,我们不希望在完全无需其结果的情况调用函数,不过仍然希望只调用函数一次。
现在这份代码能够应对我们的需求了,但数据科学部门的同学告知我们将来会对调用 `simulated_expensive_calculation` 的方式做出一些改变。为了在要做这些改动的时候简化更新步骤,我们将重构代码来让它只调用 `simulated_expensive_calculation` 一次。同时还希望去掉目前多余的连续两次函数调用,并不希望在计算过程中增加任何其他此函数的调用。也就是说,我们不希望在完全无需其结果的情况调用函数,不过仍然希望只调用函数一次。
#### 使用函数重构
有多种方法可以重构此程序。我们首先尝试的是将重复的慢计算函数调用提取到一个变量中,如示例 13-4 所示:
有多种方法可以重构此程序。我们首先尝试的是将重复的 `simulated_expensive_calculation` 函数调用提取到一个变量中,如示例 13-4 所示:
<span class="filename">文件名: src/main.rs</span>
@ -229,11 +229,11 @@ fn generate_workout(intensity: u32, random_number: u32) {
闭包不要求像 `fn` 函数那样在参数和返回值上注明类型。函数中需要类型注解是因为他们是暴露给用户的显式接口的一部分。严格的定义这些接口对于保证所有人都认同函数使用和返回值的类型来说是很重要的。但是闭包并不用于这样暴露在外的接口:他们储存在变量中并被使用,不用命名他们或暴露给库的用户调用。
另外,闭包通常很短并只与对应相对任意的场景较小的上下文中。在这些有限制的上下文中,编译器能可靠的推断参数和返回值的类型,类似于它是如何能够推断大部分变量的类型一样。
闭包通常很短并只与对应相对任意的场景较小的上下文中。在这些有限制的上下文中,编译器能可靠的推断参数和返回值的类型,类似于它是如何能够推断大部分变量的类型一样。
强制在这些小的匿名函数中注明类型是很恼人的,并且与编译器已知的信息存在大量的重复。
类似于变量,如果相比严格的必要性你更希望增加明确性并变得更啰嗦,可以选择增加类型注解;为示例 13-4 中定义的闭包标注类型将看起来像示例 13-7 中的定义:
类似于变量,如果相比严格的必要性你更希望增加明确性并变得更啰嗦,可以选择增加类型注解;为示例 13-5 中定义的闭包标注类型将看起来像示例 13-7 中的定义:
<span class="filename">文件名: src/main.rs</span>
@ -298,7 +298,7 @@ error[E0308]: mismatched types
为了让结构体存放闭包,我们需要能够指定闭包的类型,因为结构体定义需要知道其每一个字段的类型。每一个闭包实例有其自己独有的匿名类型:也就是说,即便两个闭包有着相同的签名,他们的类型仍然可以被认为是不同。为了定义使用闭包的结构体、枚举或函数参数,需要像第十章讨论的那样使用泛型和 trait bound。
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在下一部分捕获环境部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
`Fn` 系列 trait 由标准库提供。所有的闭包都实现了 trait `Fn`、`FnMut` 或 `FnOnce` 中的一个。在“闭包会捕获其环境”部分捕获环境部分我们会讨论这些 trait 的区别;在这个例子中可以使用 `Fn` trait。
为了满足 `Fn` trait bound 我们增加了代表闭包所必须的参数和返回值类型的类型。在这个例子中,闭包有一个 `u32` 的参数并返回一个 `u32`,这样所指定的 trait bound 就是 `Fn(u32) -> u32`
@ -529,15 +529,15 @@ error[E0434]: can't capture dynamic environment in a fn item; use the || { ...
当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。这会使用内存并产生额外的开销,当执行不会捕获环境的更通用的代码场景中我们不希望有这些开销。因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和可变借用。这三种捕获值的方式被编码为如下三个 `Fn` trait
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和可变借用。这三种捕获值的方式被编码为如下三个 `Fn` trait
* `FnOnce` 消费从周围作用域捕获的变量,闭包周围的作用域被称为其 **环境***environment*。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的 `Once` 部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。
* `Fn` 从其环境不可变的借用值
* `FnMut` 可变的借用值所以可以改变其环境
* `FnMut` 获取可变的借用值所以可以改变其环境
* `Fn` 从其环境获取不可变的借用值
当创建一个闭包时Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。在示例 13-12 中,`equal_to_x` 闭包不可变的借用了 `x`(所以 `equal_to_x` 使用 `Fn` trait因为闭包体只需要读取 `x` 的值。
当创建一个闭包时Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 `FnOnce` 。那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 `FnMut` ,而不需要对被捕获的变量进行可变访问的闭包则也实现了 `Fn`在示例 13-12 中,`equal_to_x` 闭包不可变的借用了 `x`(所以 `equal_to_x` 具有 `Fn` trait因为闭包体只需要读取 `x` 的值。
如果我们希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 `move` 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
如果希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 `move` 关键字。这个技巧在将闭包传递给新线程以便将数据移动到新线程中时最为实用。
第十六章讨论并发时会展示更多 `move` 闭包的例子,不过现在这里修改了示例 13-12 中的代码(作为演示),在闭包定义中增加 `move` 关键字并使用 vector 代替整型,因为整型可以被拷贝而不是移动;注意这些代码还不能编译: