Rust 之 闭包与迭代器详解

深入理解Rust函数式编程的核心:闭包与迭代器

Posted by Vlor on December 25, 2025

概述

闭包和迭代器是Rust函数式编程的两大核心支柱,它们让代码更加简洁、高效且富有表现力。闭包允许我们创建匿名函数,捕获环境变量,实现灵活的回调机制;迭代器则提供了一种优雅的方式来处理序列数据,避免显式循环,提升代码可读性和性能。

核心概念

什么是闭包和迭代器?

  • 闭包 (Closure): 可以捕获其环境的匿名函数,能够像普通函数一样调用,同时可以访问定义它的作用域中的变量
  • 迭代器 (Iterator): 一种遍历序列元素的抽象,允许我们对集合进行链式操作,实现惰性求值
  • 函数式风格: 强调使用不可变数据和纯函数,减少副作用,提高代码的可维护性

性能优势

Rust的闭包和迭代器在编译期会被优化为高效的机器码,不会带来运行时开销:

  • 闭包会被编译为结构体,捕获的变量作为结构体字段
  • 迭代器实现了零成本抽象,性能与手动编写的循环相当

闭包详解

定义与语法

闭包使用||代替函数的(),可以捕获环境变量,无需显式声明参数和返回值类型(编译器会自动推导)。

// 简单闭包
let add = |a, b| a + b;
assert_eq!(add(2, 3), 5);

// 带类型注解的闭包
let multiply: fn(i32, i32) -> i32 = |a: i32, b: i32| -> i32 { a * b };
assert_eq!(multiply(2, 3), 6);

捕获环境变量

闭包可以捕获定义它的作用域中的变量,有三种捕获方式:

移动捕获 (Move)

let s = String::from("hello");
let closure = move || {
    println!("{}", s);
};
// s的所有权已被移动到闭包中,此处无法再使用s

可变借用捕获

let mut x = 0;
let mut closure = || {
    x += 1;
    println!("x: {}", x);
};
closure(); // x: 1
closure(); // x: 2

不可变借用捕获

let s = String::from("hello");
let closure = || {
    println!("{}", s);
};
closure(); // hello
println!("{}", s); // hello(s仍然可用)

闭包的三种trait

Rust中的闭包实现了以下三种trait之一,具体取决于其使用方式:

Trait 描述 限制
Fn 闭包通过不可变借用捕获变量 可以多次调用,不修改捕获变量
FnMut 闭包通过可变借用捕获变量 可以多次调用,修改捕获变量
FnOnce 闭包通过移动捕获变量 只能调用一次,消耗捕获变量
// Fn示例
let s = String::from("hello");
let print = || println!("{}", s);
print(); // hello
print(); // hello

// FnMut示例
let mut x = 0;
let mut increment = || x += 1;
increment();
assert_eq!(x, 1);

// FnOnce示例
let s = String::from("hello");
let consume = move || println!("{}", s);
consume(); // hello
// consume(); // 错误:闭包已被消耗

闭包作为参数和返回值

闭包作为参数

fn apply<F>(f: F) where F: FnOnce() {
    f();
}

let s = String::from("hello");
apply(move || println!("{}", s));

闭包作为返回值

fn create_closure() -> impl Fn(i32) -> i32 {
    let x = 5;
    move |y| x + y
}

let closure = create_closure();
assert_eq!(closure(3), 8);

迭代器详解

定义与作用

迭代器是一种遍历序列元素的抽象,实现了Iterator trait,核心方法是next(),返回Option<Self::Item>

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

基本迭代器

创建迭代器

// 从范围创建迭代器
let mut iter = 1..=5;
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), Some(3));
assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), None);

// 从集合创建迭代器
let vec = vec![1, 2, 3];
let mut iter = vec.iter();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next(), Some(&2));
assert_eq!(iter.next(), Some(&3));
assert_eq!(iter.next(), None);

常用迭代器适配器

map

将迭代器中的每个元素映射为另一个值

let numbers = vec![1, 2, 3];
let squared: Vec<i32> = numbers.iter().map(|&x| x * x).collect();
assert_eq!(squared, vec![1, 4, 9]);

filter

根据条件过滤元素

let numbers = vec![1, 2, 3, 4, 5];
let even: Vec<i32> = numbers.into_iter().filter(|&x| x % 2 == 0).collect();
assert_eq!(even, vec![2, 4]);

fold

将迭代器中的元素折叠为一个值

let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter().fold(0, |acc, &x| acc + x);
assert_eq!(sum, 15);

take

获取前n个元素

let numbers = vec![1, 2, 3, 4, 5];
let first_three: Vec<i32> = numbers.iter().take(3).cloned().collect();
assert_eq!(first_three, vec![1, 2, 3]);

skip

跳过前n个元素

let numbers = vec![1, 2, 3, 4, 5];
let skip_two: Vec<i32> = numbers.iter().skip(2).cloned().collect();
assert_eq!(skip_two, vec![3, 4, 5]);

chain

将两个迭代器链接起来

let a = vec![1, 2, 3];
let b = vec![4, 5, 6];
let combined: Vec<i32> = a.iter().chain(b.iter()).cloned().collect();
assert_eq!(combined, vec![1, 2, 3, 4, 5, 6]);

zip

将两个迭代器的元素配对

let a = vec![1, 2, 3];
let b = vec!["a", "b", "c"];
let zipped: Vec<(i32, &str)> = a.iter().zip(b.iter()).map(|(&x, &y)| (x, y)).collect();
assert_eq!(zipped, vec![(1, "a"), (2, "b"), (3, "c")]);

迭代器的性能优势

Rust的迭代器实现了惰性求值,只有在需要时才会计算元素,避免不必要的内存分配和计算。

惰性求值示例

let numbers = vec![1, 2, 3, 4, 5];
let iter = numbers.iter().map(|&x| x * x).filter(|&x| x > 10);
// 此时还没有进行任何计算

let result: Vec<i32> = iter.collect();
// 此时才会执行map和filter操作
assert_eq!(result, vec![16, 25]);

与显式循环的性能对比

// 显式循环
let mut sum = 0;
for i in 1..=1000 {
    sum += i;
}

// 迭代器
let sum: i32 = (1..=1000).sum();

两者在编译后的机器码几乎完全相同,性能没有差异。

闭包与迭代器的结合使用

闭包作为迭代器适配器的参数

let numbers = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = numbers.iter()
    .map(|&x| x * 2)
    .filter(|&x| x > 5)
    .collect();
assert_eq!(result, vec![6, 8, 10]);

自定义迭代器

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

let mut counter = Counter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
assert_eq!(counter.next(), Some(3));
assert_eq!(counter.next(), Some(4));
assert_eq!(counter.next(), Some(5));
assert_eq!(counter.next(), None);

实践应用

闭包在回调中的应用

use std::thread;

fn main() {
    let mut data = vec![1, 2, 3];

    thread::spawn(move || {
        data.push(4);
        println!("Thread data: {:?}", data);
    }).join().unwrap();

    // data的所有权已被移动到线程中,此处无法再使用
}

迭代器在处理大型数据中的应用

use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

fn main() {
    if let Ok(lines) = read_lines("./data.txt") {
        let total: u32 = lines
            .filter_map(|result| result.ok())
            .filter(|line| !line.is_empty())
            .filter_map(|line| line.parse().ok())
            .sum();

        println!("Total: {}", total);
    }
}

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> where P: AsRef<Path> {
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

总结要点

  1. 闭包是可以捕获环境变量的匿名函数,实现了FnFnMutFnOnce trait
  2. 迭代器是遍历序列元素的抽象,实现了Iterator trait,支持惰性求值
  3. 闭包和迭代器结合使用可以编写出简洁、高效且富有表现力的代码
  4. Rust的闭包和迭代器实现了零成本抽象,性能与手动编写的代码相当
  5. 闭包和迭代器是Rust函数式编程的核心,提高了代码的可读性和可维护性

进阶资源

Rust社区风采

https://example.com/rust-community.jpg

Rust社区致力于构建安全、高效且富有表现力的系统编程未来