闭包和迭代器看起来像语法糖,但它们背后依然是所有权、借用和 trait。
闭包是什么
闭包是一段可以保存到变量里、传给函数、从函数返回的匿名函数。
1 2 3 4 5
| fn main() { let add_one = |x| x + 1;
println!("{}", add_one(1)); }
|
|x| x + 1 就是闭包。
闭包可以捕获外部变量。
1 2 3 4 5 6
| fn main() { let base = 10; let add_base = |x| x + base;
println!("{}", add_base(5)); }
|
这里闭包使用了外部变量 base。
捕获变量
闭包捕获变量有三种方式:
例如,只读外部变量:
1 2 3 4 5 6 7 8 9 10
| fn main() { let name = String::from("rekord");
let print = || { println!("{}", name); };
print(); println!("{}", name); }
|
这里只需要不可变借用。
如果要修改外部变量:
1 2 3 4 5 6 7 8 9 10
| fn main() { let mut count = 0;
let mut inc = || { count += 1; };
inc(); println!("{}", count); }
|
这里闭包需要可变借用 count。
move 闭包
如果希望闭包拿走外部变量所有权,可以使用 move。
1 2 3 4 5 6 7 8 9 10
| fn main() { let name = String::from("rekord");
let print = move || { println!("{}", name); };
print(); }
|
move 常见于线程和异步场景,因为闭包可能比当前作用域活得更久。
fn 和函数指针
Rust 里的普通函数用 fn 定义。
1 2 3
| fn add_one(x: i32) -> i32 { x + 1 }
|
普通函数不能捕获外部变量。
如果要把函数作为值传递,可以使用函数指针类型:
1 2 3 4 5 6 7 8 9 10 11
| fn add_one(x: i32) -> i32 { x + 1 }
fn apply(f: fn(i32) -> i32, value: i32) -> i32 { f(value) }
fn main() { println!("{}", apply(add_one, 1)); }
|
这里的 fn(i32) -> i32 是函数指针类型。
普通函数可以传给需要闭包 trait 的地方,因为函数不会捕获环境。
Fn、FnMut、FnOnce
闭包会根据捕获方式自动实现不同 trait。
Fn:只读使用捕获值,可以调用多次。
FnMut:会修改捕获值,可以调用多次,但调用时需要可变闭包。
FnOnce:会消耗捕获值,只能调用一次。
这三个 trait 的区别,本质上是调用闭包时怎么使用捕获的环境。
Fn
如果闭包只读取捕获值,它通常实现 Fn。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fn call_twice<F>(f: F) where F: Fn(), { f(); f(); }
fn main() { let name = String::from("rekord");
let print = || { println!("{}", name); };
call_twice(print); }
|
print 只是不可变借用 name,所以可以调用多次。
FnMut
如果闭包会修改捕获值,它通常实现 FnMut。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fn call_twice<F>(mut f: F) where F: FnMut(), { f(); f(); }
fn main() { let mut count = 0;
let inc = || { count += 1; };
call_twice(inc); }
|
这里闭包需要可变借用 count。
所以调用闭包的函数也要把 f 声明成 mut。
FnOnce
如果闭包会消耗捕获值,它只能实现 FnOnce。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| fn call_once<F>(f: F) where F: FnOnce(), { f(); }
fn main() { let name = String::from("rekord");
let consume = || { drop(name); };
call_once(consume); }
|
drop(name) 会拿走 name 的所有权。
所以这个闭包只能调用一次。
三者还有包含关系:
- 能实现
Fn 的闭包,也能当作 FnMut 和 FnOnce 使用。
- 能实现
FnMut 的闭包,也能当作 FnOnce 使用。
- 只能实现
FnOnce 的闭包,不能当作 Fn 或 FnMut 使用。
闭包的 Copy 和 Clone
闭包本身也可能实现 Copy 或 Clone。
关键取决于它捕获了什么,以及如何捕获。
- 不捕获环境:通常
Copy + Clone。
- 只捕获
Copy 值:通常 Copy + Clone。
- 捕获
Clone 但非 Copy 的值:可能 Clone,通常不是 Copy。
- 调用时会消耗捕获值:通常只能
FnOnce,也不适合作为可复制闭包。
迭代器是什么
迭代器是一种按顺序产出值的对象。
Rust 的迭代器核心是 Iterator trait。
简化后可以理解为:
1 2 3 4 5
| trait Iterator { type Item;
fn next(&mut self) -> Option<Self::Item>; }
|
next 每次返回一个值:
Some(value):还有下一个值。
None:迭代结束。
iter、iter_mut、into_iter
集合常见三种迭代方式。
1 2 3 4 5
| let nums = vec![1, 2, 3];
for n in nums.iter() { println!("{}", n); }
|
iter() 产生不可变引用。
1 2 3 4 5
| let mut nums = vec![1, 2, 3];
for n in nums.iter_mut() { *n += 1; }
|
iter_mut() 产生可变引用。
1 2 3 4 5
| let nums = vec![1, 2, 3];
for n in nums.into_iter() { println!("{}", n); }
|
into_iter() 消耗集合,产出拥有所有权的值。
map
map 用来把每个元素转换成另一个值。
1 2 3 4 5 6 7 8
| let nums = vec![1, 2, 3];
let doubled: Vec<i32> = nums .iter() .map(|n| n * 2) .collect();
println!("{:?}", doubled);
|
map 接收一个闭包。
这里的 |n| n * 2 表示每个元素如何转换。
filter
filter 用来保留满足条件的元素。
1 2 3 4 5 6 7 8
| let nums = vec![1, 2, 3, 4];
let even: Vec<&i32> = nums .iter() .filter(|n| *n % 2 == 0) .collect();
println!("{:?}", even);
|
注意这里使用 iter(),所以收集到的是引用。
如果想拿到值,可以配合 copied():
1 2 3 4 5
| let even: Vec<i32> = nums .iter() .copied() .filter(|n| n % 2 == 0) .collect();
|
fold
fold 用来把一组值折叠成一个结果。
1 2 3 4 5 6 7
| let nums = vec![1, 2, 3, 4];
let sum = nums .iter() .fold(0, |acc, n| acc + n);
println!("{}", sum);
|
0 是初始值。
acc 是上一步累积结果。
collect
迭代器通常是惰性的。
1 2 3
| let nums = vec![1, 2, 3];
let iter = nums.iter().map(|n| n * 2);
|
这里只创建了一个迭代器,还没有真正收集结果。
需要 collect 才会变成集合:
1 2 3 4
| let result: Vec<i32> = nums .iter() .map(|n| n * 2) .collect();
|
collect 需要知道目标集合类型,所以经常要写类型标注。
for 循环也是迭代器
for 循环本质上也是基于迭代器。
1 2 3
| for n in vec![1, 2, 3] { println!("{}", n); }
|
可以理解为不断调用 next(),直到返回 None。
总结
- 闭包是可以捕获环境的匿名函数。
- 闭包捕获变量时仍然遵守所有权和借用规则。
move 闭包会拿走捕获变量所有权。
Fn、FnMut、FnOnce 描述闭包如何使用捕获值。
- 迭代器核心是
Iterator trait。
iter 借用元素。
iter_mut 可变借用元素。
into_iter 消耗集合。
map 转换元素。
filter 过滤元素。
fold 累积结果。
collect 把迭代器收集成集合。