什么是闭包 Closure
Rust 是一个比较新的编程语言(00后编程语言), 它在设计的时候 参考了很多已经存在的不同风格的编程语言, 其中就包括函数式编程风格
函数式编程风格通常包含将函数作为参数值或其他函数的返回值, 将函数赋值给变量以供之后执行等等
但是 Rust 不同于一些脚本语言(比如Lua/JS/PHP), 可以直接将函数当作参数/返回值来当作闭包使用 因为在 Rust 中函数并不是闭包
, 虽然写法和用法上类似, 但这是两个不同的东西
闭包(Closure): 一个可以存储在变量里的可执行结构(类似函数)
- 匿名函数
- 保存为变量/作为参数/作为返回值
- 可以在一个地方创建闭包, 然后再另外一个上下文中调用闭包
使用闭包定义行为抽象
Rust设计语言这个章节, 说白了就是在说: 为什么要使用闭包?
简单来说, 我需要固定执行一些代码, 然后执行(x), 最后的结果会根据 x 的不同而不同, 举个例子吧, 我需要从一组数字中, 分别获取偶数和奇数:
rust
fn main() {
let nums: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
let mut even_nums = vec![];
for n in nums.iter() {
// 先不用管 iter() 函数是什么,
// 只需要知道这样操作可以遍历就行
if n % 2 == 0 {
even_nums.push(n);
}
}
let mut odd_nums = vec![];
for n in nums.iter() {
if n % 2 == 1 {
odd_nums.push(n);
}
}
println!("{:?}", even_nums);
println!("{:?}", odd_nums);
}
这个代码就是将 Vec 所有元素都遍历一遍, 然后根据一个条件(x)来筛选元素, 将所有符合 条件(x)的元素收集到一个新的 Vec 中, 可以看出, 仅仅是筛选的条件不同, 其他代码没有任何变化, 那么能否将筛选条件逻辑当作参数动态的传入? 实现行为的抽象
rust
fn filter<F>(nums: &[u32], filter_cn: F) -> Vec<u32>
where F: Fn(u32) -> bool // 声明这个匿名函数(也就是闭包)的参数和返回值
{
let mut filtered: Vec<u32> = Vec::new();
for n in nums.iter() {
if filter_cn(*n) {
filtered.push(*n);
}
}
filtered
}
fn main() {
let nums: Vec<u32> = vec![1, 2, 3, 4, 5, 6];
// 将判断的行为当作参数传入就大大提高了代码复用性和可读性
let even_nums = filter(&nums, |n| n % 2 == 0);
let odd_nums = filter(&nums, |n| n % 2 == 1);
println!("{:?}", even_nums);
println!("{:?}", odd_nums);
}
将一个行为(可能是判断/也可能是其他操作)动态的传入另外一个函数 就叫做: 定义行为抽象
闭包的类型推断和标注
和函数非常类似, 但是有些情况下, 闭包不需要声明返回值和参数的数据类型
rust
// 函数
fn f1(x:i32) -> i32 {
x + 1
}
// 闭包(指定参数和返回值类型)
let c1 = |x:i32| -> i32 {
x + 1
}
// 闭包(未声明参数和返回值类型)
let c2 = |x| { x + 1 }
// 闭包(只有一句代码的时候可以省略{}
// 类似 python 的 lambda 表达式, JS 的箭头函数
let c3 = |x| x + 1
使用带有泛型和 Fn Trait 的闭包
- 定义 struct 需要知道所有的字段类型
- 需要指定闭包的类型
- 每个闭包的实例都有自己唯一的匿名类型, 所有需要使用泛型
- Fn Trait: 所有闭包都至少实现了一个 Fn Trait, 只需要约束泛型的类型为其中一个 Trait 即可
rust
struct Array<T> {
items: Vec<T>,
}
impl<T> Array<T> {
// 从 Vec 中所有所有元素, 创建一个 Array 实例
fn from(items: Vec<T>) -> Self {
Self {
items,
}
}
// 对数组进行过滤
fn filter<F>(&self, filter_fn: F) -> Vec<T>
where F: Fn(&T) -> bool,
T: Clone,
// Fn(&T) -> bool: 限制闭包的参数和返回值类型
// &T 表示传入的数据类型的引用, 比如(i32) 那么参数类型就是 &i32
// 限制泛型必须实现 Clone 这个 trait, 否则可能没有 clone 方法
{
let mut filtered_items = Vec::new();
for item in self.items.iter() {
if filter_fn(item) {
filtered_items.push(item.clone());
}
}
filtered_items
}
}
fn main() {
let array = Array::from(vec![1, 2, 3, 4]);
let filtered = array.filter(|&x| x % 2 == 0);
// 其实也可以简写为:
// let odd_nums = Array::from(vec![1, 2, 3, 4]).filter(|&x| x % 2 == 1);
println!("{:?}", filtered);
}
闭包会捕获所在环境
rust
fn main() {
let num = 10;
// 这个闭包捕获了 num 的值
let increment = |x: i32| num + x;
// 所以才能运算出想要的结果
let num2 = increment(10);
// 注意: 函数是不能捕获外部环境的, 只有闭包才能捕获外部环境
// 但是: 捕获环境会产生额外的内存开销, 而函数不会
// error[E0434]: can't capture dynamic environment in a fn item
// fn decrement(x: i32) {
// num - x
// }
println!("num2 = {}", num2); // 20
}
函数从所在环境捕获值的方式
- Fn: 只读引用
- FnMut: 可变引用
- FnOnce: 取得所有权
创建闭包时, 通过闭包对值得使用, Rust编译器可以推断出具体使用哪个 Trait
- 所有闭包都实现了 FnOnce
- 没有移动捕获变量的实现了 FnMut
- 无需可变范文捕获变量的闭包实现了 Fn
rust
struct CaptureExample {
value: i32,
}
impl CaptureExample {
fn new(value: i32) -> Self {
Self { value }
}
fn fn_closure<F>(&self, f: F)
where
F: Fn(i32) -> i32,
{
let result = f(self.value);
println!("Result: {}", result);
}
fn fn_once_closure<F>(&mut self, f: F)
where
F: FnOnce(&mut i32) -> i32,
{
let result = f(&mut self.value);
println!("Result with once closure: {}", result);
}
}
fn main() {
let mut ce = CaptureExample::new(10);
// 使用闭包捕获环境, 传入只读引用
ce.fn_closure(|val| val + 10);
// 使用闭包捕获环境, 传入可变引用
ce.fn_once_closure(|val| {
*val += 10; // 需要手动解引用
*val
});
}
move关键字
在参数列表前使用 move 关键字, 可以强制闭包取得他所捕获的环境值的所有权
- 当闭包传递给新线程以移动数据使其所有权归新线程时非常有用
rust
fn main() {
let nums: Vec<i32> = vec![1, 2, 3];
let eq_to_nums = move |new_nums: Vec<i32>| new_nums == nums;
// move 关键字会让被捕获的值(nums)的所有权移交给这个闭包
// 所以后续就无法再输出 nums
// println!("不能再使用 nums {:?}", nums);
let anthor_nums = vec![1,2,3];
println!("nums eq anthor_nums, {}", eq_to_nums(anthor_nums))
}