十一、函数式语言特性:迭代器和闭包
Rust的设计灵感来自于许多现有的语言和技术,其中一个重要影响是函数式编程( functional programming)。函数式风格的编程通常包括将函数作为值使用,方法是将它们传递到参数中,从其他函数返回它们,将它们赋值给变量以便稍后执行,等等。
在本章中,我们不讨论函数式编程是什么或不是什么,而是讨论Rust的一些特性,这些特性类似于许多语言中通常称为函数式的特性。
更具体地说,我们将介绍:
- 闭包
Closures
,一个类似函数的构造,可以存储在变量中 - 迭代器
Iterators
,一种处理一系列元素的方法 - 如何使用闭包和迭代器来改进第12章的I/O项目
- 闭包和迭代器的性能(剧透:它们比你想象的要快!)
因为掌握闭包和迭代器是编写地道的、快速的Rust代码的重要部分,所以我们将用整章的时间来讨论它们。
11.1 闭包:捕获其环境的匿名函数
Rust的闭包是匿名函数,可以保存在变量中,也可以作为参数传递给其他函数。您可以在一个地方创建闭包,然后在其他地方调用闭包,以在不同的上下文中计算它。与函数不同,闭包可以从定义它们的作用域捕获值。我们将演示这些闭包特性如何支持代码重用和行为我自定义。
11.1.1 使用闭包捕获环境
我们将首先研究如何使用闭包从定义闭包的环境中捕获值,以供以后使用。场景是这样的:每隔一段时间,我们的t恤公司就会向我们邮件列表上的某个人赠送一件独家限量版衬衫作为促销。邮件列表上的用户可以选择将自己喜欢的颜色添加到个人资料中。如果被选为免费衬衫的人有他们最喜欢的颜色,他们就会得到那种颜色的衬衫。如果这个人没有指定最喜欢的颜色,他们就得到公司目前最多的颜色。
有许多方法可以实现这一点。对于本例,我们将使用一个名为ShirtColor
的枚举,它具有变体Red
和Blue
(为简单起见,限制了可用颜色的数量)。我们用一个inventory
结构体表示公司的库存,该结构体有一个名为shirts
的字段,该字段包含Vec<ShirtColor>
,表示当前库存的衬衫颜色。在Inventory
中定义的方法giveaway
获得免费衬衫获胜者的可选衬衫颜色偏好,并返回该人将获得的衬衫颜色。这个设置如清单13-1所示:
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {Red,Blue,
}struct Inventory {shirts: Vec<ShirtColor>,
}impl Inventory {fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {user_preference.unwrap_or_else(|| self.most_stocked())}fn most_stocked(&self) -> ShirtColor {let mut num_red = 0;let mut num_blue = 0;for color in &self.shirts {match color {ShirtColor::Red => num_red += 1,ShirtColor::Blue => num_blue += 1,}}if num_red > num_blue {ShirtColor::Red} else {ShirtColor::Blue}}
}fn main() {let store = Inventory {shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],};let user_pref1 = Some(ShirtColor::Red);let giveaway1 = store.giveaway(user_pref1);println!("The user with preference {:?} gets {:?}",user_pref1, giveaway1);let user_pref2 = None;let giveaway2 = store.giveaway(user_pref2);println!("The user with preference {:?} gets {:?}",user_pref2, giveaway2);
}
main
定义的store
有两件蓝色衬衫和一件红色衬衫,以分发本次限量版促销。对于一个喜欢穿红衬衫的用户和一个不喜欢穿红衬衫的用户,我们调用giveaway
方法。
同样,这段代码可以通过多种方式实现,在这里,为了关注闭包,除了giveaway
使用闭包的方法之外,我们一直坚持您已经学过的概念。在giveaway
方法中,我们获得用户首选项作为类型Option<ShirtColor>
并在user_preference
上调用unwrap_or_else
方法(Option<T>
由标准库定义)。它有一个参数:一个没有任何参数的闭包,返回值T
(与Option<T>
的Some变体中存储的类型相同,在本例中是ShirtColor
)。如果Option<T>
是Some变量,unwrap_or_else从Some变量中返回值。如果Option<T>
是None变量,unwrap_or_else调用闭包并返回由闭包返回的值。
我们指定闭包表达式|| self. most_stored()
作为unwrap_or_else
的参数。这是一个闭包,它本身没有参数(如果闭包有参数,它们将出现在两个竖线之间)。闭包的主体调用self. most_stocking()
。我们在这里定义闭包,如果需要结果,unwrap_or_else
的实现稍后将计算闭包。
Running this code prints:
$ cargo runCompiling shirt-company v0.1.0 (file:///projects/shirt-company)Finished dev [unoptimized + debuginfo] target(s) in 0.27sRunning `target/debug/shirt-company`
The user with preference Some(Red) gets Red
The user with preference None gets Blue
这里一个有趣的方面是,我们在当前Inventory
实例上传递了一个调用self. most_stocking()
的闭包。标准库不需要知道我们定义的Inventory
或ShirtColor
类型的任何信息,也不需要知道我们想在这个场景中使用的逻辑。闭包捕获对self
Inventory
实例的不可变引用,并将其与我们指定的代码一起传递给unwrap_or_else
方法。另一方面,函数不能以这种方式捕获它们的环境。
11.1.1 闭包类型推断和注释
函数和闭包之间有更多的区别。闭包通常不需要像fn
函数那样注释形参或返回值的类型。函数上需要类型注释,因为类型是向用户公开的显式接口的一部分。严格定义这个接口对于确保所有人都同意函数使用和返回的值类型非常重要。另一方面,闭包不会在这样的公开接口中使用:它们存储在变量中,在使用时不给它们命名,也不向库的用户公开它们。