Rust 闭包学习笔记
一、闭包捕获环境变量的三种方式
Rust 的闭包可以从其定义环境中捕获变量,捕获方式取决于闭包内部对变量的使用方式。主要有以下三种:
- 以不可变借用 &T 捕获
- 以可变借用 &mut T 捕获
- 以所有权 T 捕获
Rust 会根据闭包内部的操作自动推断需要的捕获方式。
1 不可变捕获:
let color = String::from("blue");
let print = || {
println!("Color is {}", color);
};
这里闭包只读取 color,所以捕获方式为不可变借用 &T。
调用闭包之后,依然可以继续以不可变方式借用 color。
2 可变捕获:
let mut count = 0;
let mut increment = || {
count += 1;
println!("count is now {}", count);
};
因为闭包改变了 count,所以捕获方式变为 &mut。
同时,闭包本身需要用 mut 绑定,因为调用闭包会改变其内部状态。
3 所有权捕获:
let movable = Box::new(1);
let consume = || {
println!("movable is now {}", movable);
mem::drop(movable);
};
这里闭包对 movable 调用了 drop,因此闭包以 T 的方式捕获了变量,并且闭包只能调用一次。
二、使用 move 强制所有权捕获
在闭包定义前使用 move 关键字,可以强制闭包获得捕获变量的所有权。
let v = vec![1,2,3];
let my_contains = move |element| -> bool {
v.contains(element)
};
此时 v 的所有权已移交给闭包,外部不可再访问 v。
三、闭包作为函数参数:Fn / FnMut / FnOnce
Rust 使用三个 trait 来描述闭包的捕获方式:
- Fn:闭包捕获变量的方式只能是不可变借用(&T)
- FnMut:闭包可能捕获可变借用(&mut T)
- FnOnce:闭包可能捕获所有权(T)
其中 FnOnce 的等级最高,Fn的等级最低。
// 闭包作为函数参数时,需要以 泛型+trait 来实现
fn apply<T>(f: T) where
T: FnOnce() {
f();
}
let greeting = "hello";
let mut farewell = "goodbye".to_owned();
let diary = || {
// 不可变借用,属于 Fn
println!("I said {}", greeting);
// 可变借用,使闭包升级为 FnMut
farewell.push_str("hahahaha");
// 所有权消耗,使闭包升级为 FnOnce
mem::drop(farewell);
};
apply(diary);
let do_nothing = || {println!(""); };
// apply的参数等级是FnOnce,当然能接受一个低等级的Fn(反过来就不行了!)
apply(do_nothing);
四、闭包作为函数返回值
闭包可以作为函数的输入,自然也可以作为函数的输出。函数可以返回一个闭包,但闭包必须使用 move 捕获外部变量,以避免悬垂引用。
fn create_fn() -> impl Fn(u32) -> u32 {
let ret = move |x: u32| {
x + 1
};
ret
}
let my_fn = create_fn();
println!("{}", my_fn(1)); // 输出 2
五、闭包在迭代器中的实际应用
迭代器大量依赖闭包,例如 any、find 等。
1 any
println!("2 in vec1: {}", vec1.iter().any(|&x| x == 2));
println!("2 in vec2: {}", vec2.into_iter().any(|x| x == 2));
- iter() 产生的是 &i32,所以闭包需要 &x 来模式匹配
- into_iter() 产生的是 i32 值,因此闭包直接 |x| 即可
2 find
println!("Find 2 in vec1: {:?}", iter.find(|&&x| x == 2));
println!("Find 2 in vec2: {:?}", into_iter.find(|&x| x == 2));
iter() 返回 &i32,find 提供的是 &&i32,因此需要 |&&x|。