十五. 迭代器与闭包
1. 闭包:能够捕获环境的匿名函数
Rust 中的闭包是一种匿名函数,它可以被存入变量或作为参数传递给其他函数。你可以在一个地方创建闭包,然后在不同的上下文中调用它来执行运算。与普通函数不同,闭包的关键特性在于它能够从其被定义的作用域中捕获并使用外部环境的值。后续将展示如何利用这些特性来实现代码复用和行为自定义。
①使用闭包捕获环境
// 示例 13-1: T恤公司的赠送方案
// 1. 定义颜色枚举 ShirtColor
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
Red,
Blue,
// 为简单起见,只使用两种颜色
}
// 2. 定义库存结构体 Inventory
struct Inventory {
shirts: Vec<ShirtColor>, // 库存T恤颜色列表
}
impl Inventory {
// giveaway 方法: 根据用户偏好返回T恤颜色
// 参数 user_preference: 用户颜色偏好,有值则送对应颜色,无值则送库存最多的颜色
fn giveaway(
&self,
user_preference: Option<ShirtColor>,
) -> ShirtColor {
// 使用闭包: 当user_preference为None时,调用self.most_stocked()获取库存最多的颜色
// 闭包 || self.most_stocked() 捕获当前Inventory实例的引用
user_preference.unwrap_or_else(|| self.most_stocked())
}
// most_stocked 方法: 统计并返回库存最多的颜色
fn most_stocked(&self) -> ShirtColor {
let mut num_red = 0;
let mut num_blue = 0;
// 遍历库存,统计红蓝T恤数量
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
}
}
}
// 3. 主函数演示
fn main() {
// 创建库存实例,包含2件蓝色和1件红色T恤
let store = Inventory {
shirts: vec![
ShirtColor::Blue,
ShirtColor::Red,
ShirtColor::Blue,
],
};
// 用户1: 有颜色偏好(红色)
let user_pref1 = Some(ShirtColor::Red);
let giveaway1 = store.giveaway(user_pref1);
println!(
"用户偏好 {:?} 获得 {:?}",
user_pref1, giveaway1
);
// 输出: 用户偏好 Some(Red) 获得 Red
// 用户2: 无颜色偏好
let user_pref2 = None;
let giveaway2 = store.giveaway(user_pref2);
println!(
"用户偏好 {:?} 获得 {:?}",
user_pref2, giveaway2
);
// 输出: 用户偏好 None 获得 Blue
}
// 核心要点:
// 1. 闭包 || self.most_stocked() 捕获了当前 Inventory 实例的不可变引用
// 2. unwrap_or_else 方法在需要时才执行闭包,实现惰性求值
// 3. 闭包可以捕获其定义环境中的值,这是普通函数无法做到的
// 4. 此设计将业务逻辑(如何选择颜色)与标准库方法解耦,提高代码复用性②闭包的类型推断和标注
// 函数与闭包在类型标注上的主要差异:
// 1. 函数必须标注参数和返回值类型,因为它们是公开接口的一部分,需要明确约定。
// 2. 闭包通常存储在变量中,不用于公开接口,且通常很短小,编译器可推断其类型,因此通常无需标注。
// 但在需要明确性或特殊场景下,仍可为闭包手动添加类型标注。
// 示例 13-2: 展示为闭包添加可选类型标注的四种等效写法
// 以下四个闭包都实现为参数加1并返回的功能,展示了从完全标注到完全推断的渐进变化
let add_one_v1 = |x: u32| -> u32 { x + 1 }; // 完整标注类型
let add_one_v2 = |x| { x + 1 }; // 省略类型标注
let add_one_v3 = |x| x + 1; // 省略花括号(单个表达式)
let add_one_v4 = |x| x + 1; // 同v3,强调类型需在调用时推导
// 类比:闭包的类型推导类似于 `let v = Vec::new()`,
// 需要在调用时通过使用上下文来推断具体类型。
// 示例 13-3: 演示闭包类型推导的具体性
// 闭包定义中的每个参数和返回值都会被推导为具体的、单一的类型
let example_closure = |x| x; // 未标注类型,可接受任意类型调用
// 第一次用 String 调用,编译器将 x 的参数和返回类型都推导为 String
let s = example_closure(String::from("hello"));
// 第二次尝试用整数调用,会导致编译错误
// 错误信息:期望 String 类型,实际找到整数
// 因为闭包 example_closure 的类型已固定为接收并返回 String
let n = example_closure(5); // 编译错误:类型不匹配
// 核心总结:
// 1. 闭包类型标注可选,编译器通常可推断。
// 2. 但每个闭包实例的参数和返回值类型是具体且唯一的,一旦通过使用推导出类型,就无法再改变。
// 3. 这与函数的严格类型标注形成对比,体现了闭包作为"匿名函数"的灵活性与限制。③捕获引用或所有权
二十. 其他
1. 注释
Rust 使用双斜杠
//来编写注释,编译器会忽略这些内容。注释的主要作用是向阅读代码的人提供解释和说明。注释通常有两种放置方式:一是放在代码行的末尾,对同一行代码进行说明;更常见的格式是在需要说明的代码块上方单独放置一行或多行注释。当注释需要跨越多行时,必须在每一行的开头都加上
//。Rust 还有一种功能更强大的文档注释,专门用于生成项目文档,这将在后续章节中详细介绍。