闭包与迭代器-Rust

闭包

定义格式

|参数|{语句}

closure,一种匿名函数,可以储存在变量里的类似函数的结构

1
2
3
4
5
6
fn main() {
let x =1;
let sum=|y|x+y;//y函数参数,x+y函数体

println!("{}",sum(2));
}

image-20250424143824898

闭包类型推导

Rust是静态语言,所有的变量都有类型

可以显示的为闭包定义类型

1
2
3
4
5
6
7
fn main() {

let sum=|x:i32,y:i32|->i32{x+y};

println!("{}",sum(2,3));

}

省略的写法

1
2
3
4
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;

闭包不会作为接口提供给外部使用,所以在定义的时候可以不确认参数类型,让编译器帮我们推导参数类型,需要注意的是,根据闭包调用时参数的类型进行判断,类型确认后就不可以修改了

image-20250424145149550

闭包中最后一行表达式返回的值,就是闭包执行后的返回值

结构体当中的闭包

实现一个简易缓存,获取一个值,将其缓存

1、一个闭包用于获取值

2、一个变量,用于存储数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 定义一个泛型结构体 Cacher,用于缓存闭包的运算结果
// T 是泛型参数,且必须满足 Fn(u32) -> u32 特征(即接受 u32 参数并返回 u32 的闭包)
struct Cacher<T>
where
T: Fn(u32) -> u32, // 泛型约束:T 必须是实现了 Fn(u32) -> u32 的闭包
{
query: T, // 存储闭包
value: Option<u32>, // 存储缓存结果,初始为 None
}

// 为 Cacher<T> 实现方法
impl<T> Cacher<T>
where
T: Fn(u32) -> u32, // 再次声明泛型约束(Rust 要求 impl 块中重复声明)
{
// 构造函数:用给定的闭包初始化 Cacher
fn new(query: T) -> Cacher<T> {
Cacher {
query, // 将传入的闭包存入结构体
value: None, // 初始化缓存值为空
}
}

// 获取缓存值的方法(需可变借用,因为可能修改缓存)
// 如果已有缓存值,直接返回;否则调用闭包计算并缓存结果
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v, // 有缓存值时直接返回
None => {
// 无缓存值时调用闭包计算
let v = (self.query)(arg); // 通过闭包计算新值,在 Rust 中,闭包作为结构体字段时,​必须用括号包裹闭包变量
self.value = Some(v); // 将结果存入缓存
v // 返回计算结果
}
}
}
}

// 主函数(当前为空,用于代码结构演示)
fn main() {
// 实际使用示例可在此添加:
// let mut cacher = Cacher::new(|x| x + 1);
// println!("Result: {}", cacher.value(5)); // 第一次计算并缓存 6
// println!("Cached: {}", cacher.value(10)); // 仍然返回 6(注意:这里存在逻辑问题)
}

方法调用self.method(arg)(调用结构体的方法)

闭包调用(self.closure)(arg)(调用结构体的闭包字段)

捕获作用域的值

闭包可以捕获调用者作用域的局部变量,而函数不可以捕捉上层函数的局部变量

image-20250424145022083三种Fn特性,函数参数的三种传入方式:转移所有权、可变借用、不可变借用

FnOnce,该类型的闭包会拿走被捕获变量的所有权。

FnMut,它以可变借用的方式捕获了环境中的值

Fn 特征,它以不可变借用的方式捕获环境中的值

三种特征的关系

  • 所有的闭包都自动实现了 FnOnce 特征,因此任何一个闭包都至少可以被调用一次
  • 没有移出所捕获变量的所有权的闭包自动实现了 FnMut 特征
  • 不需要对捕获变量进行改变的闭包自动实现了 Fn 特征

一个闭包实现了哪种 Fn 特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们

闭包作为函数返回值

1
2
3
4
5
6
7
8
9
10
fn factory() -> Fn(i32) -> i32 {//报错
let num = 5;

|x| x + num
}

let f = factory();

let answer = f(1);
assert_eq!(6, answer);

Rust 要求函数的参数和返回类型,必须有固定的内存大小,根据编译器提示,可以修改为

1
2
3
4
5
6
7
8
9
10
11
fn factory(x:i32) -> impl Fn(i32) -> i32 {//if 和 else 分支中返回了不同的闭包类型

let num = 5;

if x > 1{
move |x| x + num//报错
} else {
move |x| x - num//就算签名一样的闭包,类型也是不同的
}
}

迭代器

一种处理元素序列的方式,迭代器就是用来按顺序访问集合中的元素(比如数组、列表、文件内容等)的工具

for循环与迭代器

迭代器跟 for 循环颇为相似,都是去遍历一个集合,但是实际上它们存在不小的差别,其中最主要的差别就是:是否通过索引来访问集合

如下是js的循环

image-20250424165113017

rust循环

1
2
3
4
5
6
fn main() {
let arr = [1, 2, 3];
for v in arr {
println!("{}",v);
}
}

区别在于Rust没有使用索引,他把arr当成了迭代器,直接遍历其中的元素

惰性初始化

1
2
3
4
5
6
7
8
9
10
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter = v1.iter();//创建了一个迭代器 v1_iter,此时不会发生任何迭代行为

for val in v1_iter {//有在 for 循环开始后,迭代器才会开始迭代其中的元素
println!("{}", val);
}

}

惰性初始化的方式确保了创建迭代器不会有任何额外的性能损耗

next方法

迭代器之所以成为迭代器,就是因为实现了 Iterator 特征(trait),要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item

for 循环通过不停调用迭代器上的 next 方法,来获取迭代器中的元素。

1
2
3
4
5
6
7
8
9
fn main() {
let arr = [1, 2, 3];
let mut arr_iter = arr.into_iter();//将 arr 转换成迭代器

assert_eq!(arr_iter.next(), Some(1));
assert_eq!(arr_iter.next(), Some(2));
assert_eq!(arr_iter.next(), Some(3));
assert_eq!(arr_iter.next(), None);
}

next 方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None

  • next 方法返回的是 Option 类型,当有值时返回 Some(i32),无值时返回 None

  • 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 Some(1)Some(2)Some(3)

  • 手动迭代必须将迭代器声明为 mut 可变,因为调用 next 会改变迭代器其中的状态数据(当前遍历的位置等),而 for 循环去迭代则无需标注 mut,因为它会帮我们自动完成

    IntoIterator 特征

    迭代器自身也实现了 IntoIterator,标准库

1
2
3
4
5
6
7
fn main() {
let values = vec![1, 2, 3];

for v in values.into_iter().into_iter().into_iter() {//连续调用三次`into_iter()`其实相当于只调用一次
println!("{}",v)
}
}

into_iter, iter, iter_mut

在之前的代码中,我们统一使用了 into_iter 的方式将数组转化为迭代器,除此之外,还有 iteriter_mut,聪明的读者应该大概能猜到这三者的区别:

  • into_iter 会夺走所有权,返回具有所有权的值
  • iter 是借用
  • iter_mut 是可变借用

into_ 之类的,都是拿走所有权,_mut 之类的都是可变借用,剩下的就是不可变借用

Iterator 和 IntoIterator 的区别

Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next

IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iteriter 等方法变成一个迭代器。

消费者与适配器

调用next方法的方法被称为消耗型适配器,消耗这些方法会消耗item(迭代器成员)

sum方法,消耗型适配器

image-20250425101018069

map方法,迭代器适配器

不会消耗原始迭代器,通过修改原始迭代器生成新的迭代器

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter= v1.iter().map(|x|x+1);//产生新的迭代器,但是迭代器没有调用

let v2:Vec<_>=v1_iter.collect();//消耗新的迭代器

for i in v2{
println!("{}",i);
}
}

image-20250425101734128

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn main() {
let v1 = vec![1, 2, 3];

let v1_iter= v1.iter().map(|x|x+1);//产生新的迭代器,但是迭代器没有调用

let v2:Vec<_>=v1_iter.collect();//消耗新的迭代器

for i in v2{
println!("{}",i);
}
for i in v1{//原有迭代器依然生效
println!("{}",i);
}
}

很多迭代器适配器都将闭包作为自己的参数,filter方法