Skip to content

闭包和迭代器

闭包

官方定义: 闭包(closures)是可以保存在变量中或作为参数传递给其他函数的匿名函数

  • || 代表一个闭包,|x| x + 1 代表一个接受一个参数并返回该参数加1的闭包
  • Rust的闭包本质是一个匿名函数,是一个语法糖,它背后是一个实现了 FnFnMutFnOnce trait 的匿名类型
  • 不同的是它能够捕获上下文的变量

闭包工作原理

闭包是个"隐形结构体"

当你写下这段代码时:

rust
let x = 10;
let add_x = |y| x + y; // 这是一个闭包

编译器在幕后其实为你偷偷写了一个结构体:

rust
// 编译器生成的"隐形"结构体
struct Closure {
    x: i32, // 它把外面的 x "抓"进来了
}

// 编译器为它"重载"了括号运算符(实现 Fn Trait)
impl Fn(i32) -> i32 for Closure {
    fn call(&self, y: i32) -> i32 {
        self.x + y
    }
}

推断和注解闭包类型

  • 闭包通常不要求像 fn 函数那样对参数和返回值进行类型注解,大部分情况下编译器会根据闭包的使用情况推断出类型
rust
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  ;
  • 对于闭包定义,编译器会为每个参数和返回值推断出一个具体类型,不可以在同一闭包中使用不同类型的参数或返回值
rust
let example_closure = |x| x;

let s = example_closure(String::from("hello"));
let n = example_closure(5); // 错误: 尝试给闭包使用不同的类型

第一次使用 String 值调用 example_closure 时,编译器推断出 x 的类型以及闭包的返回类型为 String.接着这些类型被锁定进闭包 example_closure 中,如果尝试对同一闭包使用不同类型则会得到类型错误.

捕获引用或移动所有权

  • 闭包可以捕获其环境中的变量,捕获方式有三种:不可变借用可变借用获取所有权
  • 闭包将根据函数体中对捕获值的操作来自动决定使用哪种方式
rust
let x = 5;
let equal_to_x = |z| z == x; // 闭包捕获了 x 的不可变引用

let mut y = 5;
let mut add_to_y = |z| y += z; // 闭包捕获了 y 的可变引用

let s = String::from("hello");
let consume_s = || drop(s); // 闭包获取了 s 的所有权

// 使用 move 关键字强制闭包获取所有权
let m = 5;
let consume_m = move |z| m + z;

闭包的三种 Trait

  • 闭包对环境变量的捕获方式决定了它实现的 Trait
  • 函数在使用闭包时会根据所需的 Trait 来约束参数
Trait捕获方式适用场景约束
Fn不可变借用只需要读取环境变量的值最严,只读
FnMut可变借用需要修改环境变量的值中等,可读可写
FnOnce获取所有权需要获取环境变量的所有权最松,可移动

FnFnMut 的子 trait,FnMutFnOnce 的子 trait.

实现 Fn 的闭包也自动实现 FnMutFnOnce.

这里的继承是"约束的继承", 而不是"能力的扩张",实现了Fn的约束也就满足了FnMutFnOnce的约束,所以实现了Fn就同时实现了FnMutFnOnce

将捕获的值移出闭包

  • 闭包默认情况下无法直接将捕获的所有权移出,但可以通过以下技巧实现:
rust
let s = String::from("hello");
let consume_s = || drop(s); // 闭包获取了 s 的所有权并在内部消费

// 通过 Option<T> 和 take() 方法来移出值
let mut optional_s = Some(String::from("hello"));
let consume_optional_s = || optional_s.take(); // 返回 Option,可获取所有权

迭代器

官方定义: 迭代器(iterator)负责遍历序列中的每一项并确定序列何时结束的逻辑

简单来说: 只要一个类型实现了 Iterator trait,它就是迭代器

  • Rust 的迭代器是惰性的,只有最终调用 collect 等消费方法时才会真正执行迭代器的逻辑
  • 迭代器的核心是 next 方法,每次调用都会返回序列中的下一项,直到序列结束时返回 None
  • 迭代器适用于各种数据结构,不仅限于数组或向量,还可以是任何实现了 Iterator trait 的类型
  • 迭代器可以通过链式调用来组合多个操作,如过滤、映射等,从而实现复杂的数据处理逻辑

Iterator trait 和 next 方法

  • 迭代器都实现了名为 Iterator 的定义于标准库的 trait.这个 trait 的定义看起来像这样:
rust
pub trait Iterator {
    // 1. 关联类型: 告诉迭代器每次"吐出"的数据是什么类型
    type Item; // 关联类型,一个类型只能为一个类型实现一次这个 trait
     // 2. 核心方法: 定义如何获取下一个值
    fn next(&mut self) -> Option<Self::Item>;
        // 逻辑:
        // 如果还有值,返回 Some(value)
        // 如果迭代结束,返回 None
}


// 将自己的类型变成迭代器
impl Iterator for MyType {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        // 逻辑
    }
}

Iterator trait 里面定义了 next 方法,该方法每次返回迭代器中的一个项,封装在 Some 中,并且当迭代完成时,返回 None

只要你实现了 next 方法,Rust 编译器就会"自动赠送"你几十个方法(迭代器适配器),比如 .map(), .filter(), .sum(), .collect()

  • 调用 next 方法
rust
#[test]
fn iterator_demonstration() {
    let v1 = vec![1, 2, 3];

    let mut v1_iter = v1.iter(); // 创建一个迭代器,调用 iter 方法会返回一个实现了 Iterator trait 的类型,它的 Item 类型是 &i32

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

注意我们需要将 v1_iter 声明为可变的: 在迭代器上调用 next 方法会改变迭代器内部的状态,该状态用于跟踪迭代器在序列中的位置

为什么直接使用next方法需要可变引用,而消费迭代器的方法不需要

  • next 方法需要可变引用是因为它需要修改迭代器的内部状态(比如当前索引),而消费迭代器的方法(如 sum, collect)通常会获取迭代器的所有权,因此不需要可变引用
  • 在消费方法内部获取取迭代器的所有权后,将变量重新绑定为 mut , 从而内部的方法可以使用 &mut 调用 next 来遍历迭代器
rust
let v1 = vec![1, 2, 3];
let total: i32 = v1.iter().sum(); // sum 方法获取迭代器的所有权,在内部重新绑定为 mut 来调用 next 方法

// sum 方法内部的逻辑大致如下:
fn sum<S>(self) -> S
where
    Self: Sized,
    S: std::iter::Sum<Self::Item>,
{
    let mut iter = self; // 获取迭代器的所有权,重新绑定为 mut
    let mut total = S::default(); // 初始化总和

    while let Some(item) = iter.next() { // 使用 &mut iter 来调用 next 方法
        total = total + item; // 累加每个项的值
    }

    total // 返回总和
}

产生迭代器的方法

  • 简单理解就是产生一个新的迭代器的方法,比如 iter 方法会产生一个新的迭代器
  • 这些方法产生的迭代器是惰性的,只有在调用消费方法时才会真正执行迭代器的逻辑
  • 可以链式调用这些方法来组合多个操作,从而实现复杂的数据处理逻辑
  • 本质是给这些数据集合实现 IntoIterator trait,从而使它们能够被迭代器方法使用
rust
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // 需要使用 collect 方法来消费迭代器并将结果收集到一个新的向量中
assert_eq!(v2, vec![2, 3, 4]);

迭代器的三种产生方式

方法产生的类型对原集合的影响闭包里的参数
iter()&T无影响(可继续读取)&T
iter_mut()&mut T值可能被修改&mut T
into_iter()T原集合消失T (但 filter 会给 &T)

还有其他能够在迭代器上产生新迭代器的方法,比如map filter enumerate等,它们会返回一个新的迭代器,该迭代器会对原始迭代器中的每一项应用一个函数

  • iter() 方法产生一个迭代器,迭代器中的项是集合中元素的不可变引用(&T).使用 iter() 不会改变原集合,你仍然可以继续使用它来读取数据.

  • iter() 做的事情非常轻量: 申请一个记录了起始位置的小纸条(结构体),并赋予它一套"往后挪一步"的方法(next 实现)

    rust
    let v = vec![1, 2, 3];
    // 产生的是 &i32 (引用)
    let count = v.iter().count();
    println!("v 依然可用: {:?}", v);
  • iter_mut() 方法产生一个迭代器,迭代器中的项是集合中元素的可变引用(&mut T).使用 iter_mut() 可能会修改原集合中的元素.

    rust
    let mut v1 = vec![1, 2, 3];
    // 原地修改,不需要 collect,也不产生新 Vec
    v1.iter_mut().for_each(|x| *x *= 2);
    println!("v1 被原地改了: {:?}", v1); // [2, 4, 6]
  • into_iter() 方法产生一个迭代器,迭代器中的项是集合中元素的所有权(T).使用 into_iter() 会消耗原集合,原集合将不再可用.

    rust
    let v2 = vec![1, 2, 3];
    // 产生的是 i32 (所有权)
    let v2_iter = v2.into_iter().collect::<Vec<i32>>();
    println!("v2 不能再使用了: {:?}", v2); // error: borrow of moved value: `v2`

能转成迭代器的类型

  • 迭代器不仅限于数组或向量,还可以是任何实现了 Iterator trait 的类型
  • 标准库有些类型(如Range)直接实现了 Iterator trait,而大多数集合类型(Vec、HashMap等)只提供了转成成迭代器的方法(iteriter_mutinto_iter),它们本身并不是迭代器
  • 你也可以为自己的类型实现 Iterator trait,从而使它们能够被迭代器方法使用

1. 标准库中的所有集合(Collections) 几乎所有存数据的容器都能产生迭代器:

  • 线性集合: Vec<T>VecDeque<T>LinkedList<T>.
  • 键值对/集合: HashMap<K, V>BTreeMap<K, V>HashSet<T>BTreeSet<T>.
  • 原生数组: [T; N](如 [1, 2, 3])以及切片 &[T].

2. 字符串相关 字符串稍微特殊一点,因为它必须明确你是按"字节"还是按"字符"迭代:

  • String&str:
    • .chars(): 产生字符迭代器(处理 Unicode).
    • .bytes(): 产生原始字节迭代器.
    • .lines(): 按行迭代.
    • .split_whitespace(): 按空格迭代.

3. 其他常见类型

  • Option<T>Result<T, E>: 它们也实现了迭代器协议!你可以把它们看作是一个"长度最多为 1 的集合".
    • 例子: Some(5).iter() 会产生一个只吐出一次 5 的迭代器.
  • Range(范围): 如 (0..10)(0..=10).它们本身就是为了迭代而生的.

4. 外部资源(流式数据)

  • 文件/网络: 如 std::io::BufReader.lines(),它允许你逐行读取大文件而不需要一次性加载到内存.

核心判断标准:

如果你不确定一个数据能不能调 .iter(),你可以看它的文档:

  1. 它是否实现了 IntoIterator?(如果有,它就能用在 for 循环里).
  2. 它是否实现了 Deref 到切片?(比如 Vec<T> 可以自动变成 &[T],而切片是有 .iter() 的).

一句话总结:

凡是"装着一堆东西"(集合)或者"能产生一串东西"(范围、流)的类型,基本都能转换成迭代器.

常用迭代器适配器

  • 就是那些返回新迭代器的方法,调用它们不会立即执行迭代器的逻辑,而是返回一个新的迭代器,这些方法被称为迭代器适配器,因为它们适配了原始迭代器并返回一个新的迭代器
  • 惰性,返回新迭代器
方法说明
map(f)对每个元素做变换,返回新迭代器
filter(p)保留满足条件的元素
enumerate()带索引遍历,得到 (index, item)
zip(other)将两个迭代器压缩成元组迭代器 (a, b)
chain(other)连接两个迭代器,顺序遍历
flat_map(f)map 后展平一层(等价于 .map(f).flatten())
flatten()展平嵌套迭代器(Vec<Vec<T>>Vec<T>)
take(n)只取前 n 个元素
skip(n)跳过前 n 个元素
step_by(n)每隔 n 步取一个元素
peekable()让迭代器可以 .peek() 而不消耗元素
cloned()&T 迭代器转为 T 迭代器(T 需实现 Clone)
copied()&T 迭代器转为 T 迭代器(T 需实现 Copy)
rust
let v = vec![1, 2, 3, 4, 5, 6];

// enumerate + filter + map 链式组合
let result: Vec<String> = v.iter()
    .enumerate()
    .filter(|(_, x)| *x % 2 == 0)
    .map(|(i, x)| format!("索引{i}: {x}"))
    .collect();

// zip: 两个迭代器合并成元组
let names = vec!["Alice", "Bob"];
let scores = vec![95, 87];
let combined: Vec<_> = names.iter().zip(scores.iter()).collect();
// [("Alice", 95), ("Bob", 87)]

// flat_map: 展开嵌套
let words = vec!["hello world", "foo bar"];
let chars: Vec<&str> = words.iter().flat_map(|s| s.split(' ')).collect();
// ["hello", "world", "foo", "bar"]

消费迭代器的方法

  • 简单理解就是不返回迭代器本身而是返回一个具体的值的方法,比如 sum 方法会消耗迭代器并返回迭代器中所有项的总和
  • Iterator trait 有一系列不同的由标准库提供默认实现的方法;你可以在 Iterator trait 的标准库 API 文档中找到所有这些方法
  • 这些方法在其内部调用 next 方法来处理迭代器中的项,next方法本身并不会消耗迭代器,是这些方法是按值传递迭代器,所以它们会消耗迭代器(获取所有权)并返回一个具体的值
rust
#[test]
fn iterator_sum() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();
    let total: i32 = v1_iter.sum(); // sum 方法会消耗 v1_iter,并返回迭代器中所有项的总和
    assert_eq!(total, 6);
}

常用消费型方法

  • 消耗迭代器,返回具体值
方法说明
collect::<Vec<_>>()收集成集合(Vec、HashMap 等)
count()元素总数
sum() / product()求和 / 求积
any(p) / all(p)任意 / 全部满足条件 → bool
find(p)首个满足条件的元素 → Option<&T>
position(p)首个满足条件的索引 → Option<usize>
max() / min()最大 / 最小值 → Option<T>
fold(init, f)折叠累积(类似 JS 的 reduce)
for_each(f)遍历并执行副作用(等价于 for 循环)
rust
let v = vec![1, 2, 3, 4, 5];
let sum: i32 = v.iter().sum();                        // 15
let doubled: Vec<i32> = v.iter().map(|x| x * 2).collect();
let has_even = v.iter().any(|x| x % 2 == 0);         // true
let all_pos  = v.iter().all(|x| *x > 0);              // true
let first_gt3 = v.iter().find(|&&x| x > 3);           // Some(4)
let total = v.iter().fold(0, |acc, x| acc + x);       // 15(等同于 sum)

使用捕获其环境的闭包

  • 迭代器的方法通常接受一个闭包作为参数,该闭包定义了如何处理迭代器中的每一项
  • 这些闭包可以捕获其环境中的变量,从而允许你在迭代器的操作中使用外部数据
  • 例如,filter 方法接受一个闭包,该闭包返回一个布尔值来决定是否保留迭代器中的某项
rust
let v1 = vec![1, 2, 3, 4, 5];
let even_numbers: Vec<_> = v1.iter().filter(|&x| x % 2 == 0).collect();
assert_eq!(even_numbers, vec![2, 4]);

额外补充

for 循环背后的迭代器

当你直接对集合使用 for 循环时,Rust 会自动调用不同的方法:

  • for x in &vec 自动调用 vec.iter()
  • for x in &mut vec 自动调用 vec.iter_mut()
  • for x in vec 自动调用 vec.into_iter() (注意: 这会毁掉 vec)

基于 MIT 协议发布