Skip to content

Rust 闭包

闭包是 Rust 中强大而灵活的函数式编程特性,它们可以捕获周围环境中的变量,并且可以作为参数传递给其他函数。本教程将深入介绍 Rust 闭包的各种特性和使用场景。

🎯 学习目标

通过本教程,您将掌握:

  • 闭包的基本概念和语法
  • 闭包的三种捕获模式
  • 闭包类型推断和类型注解
  • 闭包在实际编程中的应用
  • 闭包与函数指针的区别
  • 高阶函数和函数式编程模式

📖 什么是闭包?

闭包的定义

闭包(Closures)是可以捕获其定义时所在环境中变量的匿名函数。与普通函数不同,闭包可以访问定义它们时作用域内的变量,这种特性被称为"捕获"。

闭包 vs 函数的对比

特性函数闭包
语法fn 名称(参数) -> 返回类型 { 函数体 }`
环境捕获不能捕获环境变量可以捕获环境变量
类型推断需要显式类型注解支持类型推断
存储函数指针三种不同的 trait 对象
性能零成本抽象根据捕获方式有不同开销

🔧 闭包基础语法

基本语法形式

rust
fn main() {
    // 最简单的闭包 - 无参数
    let 打招呼 = || println!("你好,世界!");
    打招呼();
    
    // 带一个参数的闭包
    let 平方 = |x| x * x;
    println!("5 的平方是: {}", 平方(5));
    
    // 带多个参数的闭包
    let 相加 = |a, b| a + b;
    println!("3 + 4 = {}", 相加(3, 4));
    
    // 带代码块的闭包
    let 复杂计算 = |数字| {
        println!("正在计算 {} 的复杂运算...", 数字);
        std::thread::sleep(std::time::Duration::from_millis(100));
        数字 * 数字 + 数字 * 2 + 1
    };
    
    println!("复杂计算结果: {}", 复杂计算(10));
    
    // 显式类型注解的闭包
    let 类型明确的闭包 = |x: i32| -> i32 {
        x * 2
    };
    
    println!("类型明确的闭包结果: {}", 类型明确的闭包(7));
}

类型推断示例

rust
fn main() {
    // Rust 会根据使用情况推断闭包的类型
    let 计算器 = |数值| 数值 + 1;
    
    // 第一次调用确定了参数和返回值的类型
    let 结果1 = 计算器(5i32);  // 推断为 i32
    println!("结果1: {}", 结果1);
    
    // 后续调用必须使用相同类型
    let 结果2 = 计算器(10);    // 也必须是 i32
    println!("结果2: {}", 结果2);
    
    // 不同的闭包可以有不同的类型
    let 浮点计算器 = |数值: f64| 数值 * 2.0;
    println!("浮点结果: {}", 浮点计算器(3.14));
}

📦 闭包的环境捕获

三种捕获模式

Rust 闭包有三种捕获环境变量的方式,对应三个 trait:

  1. FnOnce - 获取所有权(move)
  2. FnMut - 可变借用
  3. Fn - 不可变借用
rust
fn main() {
    println!("=== Fn trait 示例(不可变借用)===");
    {
        let 消息 = String::from("你好");
        
        // 闭包只读取变量,实现 Fn trait
        let 读取闭包 = || {
            println!("读取到的消息: {}", 消息);
        };
        
        读取闭包(); // 可以多次调用
        读取闭包();
        
        // 原变量仍然可用
        println!("原始消息仍然可用: {}", 消息);
    }
    
    println!("\n=== FnMut trait 示例(可变借用)===");
    {
        let mut 计数器 = 0;
        
        // 闭包修改变量,实现 FnMut trait
        let mut 增加计数 = || {
            计数器 += 1;
            println!("计数器值: {}", 计数器);
        };
        
        增加计数(); // 可以多次调用
        增加计数();
    }
    
    println!("\n=== FnOnce trait 示例(获取所有权)===");
    {
        let 数据 = vec![1, 2, 3, 4, 5];
        
        // 闭包获取变量所有权,实现 FnOnce trait
        let 消费闭包 = || {
            println!("消费数据: {:?}", 数据);
            数据 // 返回数据,转移所有权
        };
        
        let 返回的数据 = 消费闭包(); // 只能调用一次
        println!("返回的数据: {:?}", 返回的数据);
    }
}

move 关键字

rust
use std::thread;

fn main() {
    println!("=== 不使用 move ===");
    {
        let 数字 = 42;
        
        // 不使用 move,闭包借用变量
        let 闭包1 = || println!("借用的数字: {}", 数字);
        闭包1();
        
        // 原变量仍然可用
        println!("原始数字: {}", 数字);
    }
    
    println!("\n=== 使用 move ===");
    {
        let 文本 = String::from("重要数据");
        
        // 使用 move 强制闭包获取所有权
        let 移动闭包 = move || {
            println!("移动的文本: {}", 文本);
        };
        
        移动闭包();
        
        // 原变量不再可用
        // println!("{}", 文本); // 编译错误
    }
    
    println!("\n=== 线程中使用 move ===");
    {
        let 共享数据 = vec![1, 2, 3, 4, 5];
        
        // 在新线程中使用闭包必须使用 move
        let 句柄 = thread::spawn(move || {
            for 数值 in &共享数据 {
                println!("线程中的数值: {}", 数值);
            }
        });
        
        // 等待线程完成
        句柄.join().unwrap();
    }
}

🏗️ 高阶函数和函数式编程

接受闭包作为参数的函数

rust
// 定义一个接受闭包的高阶函数
fn 应用操作<F>(数值: i32, 操作: F) -> i32
where
    F: Fn(i32) -> i32,
{
    操作(数值)
}

// 更复杂的高阶函数
fn 过滤并映射<T, U, P, M>(集合: Vec<T>, 过滤器: P, 映射器: M) -> Vec<U>
where
    P: Fn(&T) -> bool,
    M: Fn(T) -> U,
{
    集合
        .into_iter()
        .filter(过滤器)
        .map(映射器)
        .collect()
}

fn main() {
    let 原数值 = 10;
    
    // 使用不同的闭包操作
    let 平方结果 = 应用操作(原数值, |x| x * x);
    let 立方结果 = 应用操作(原数值, |x| x * x * x);
    
    println!("原数值: {}", 原数值);
    println!("平方结果: {}", 平方结果);
    println!("立方结果: {}", 立方结果);
    
    let 数字列表 = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    
    // 过滤偶数并平方
    let 处理结果 = 过滤并映射(
        数字列表.clone(),
        |&x| x % 2 == 0,  // 过滤偶数
        |x| x * x,        // 平方
    );
    
    println!("原始数据: {:?}", 数字列表);
    println!("偶数平方: {:?}", 处理结果);
}

返回闭包的函数

rust
// 返回闭包的函数需要使用 Box<dyn Fn>
fn 创建乘法器(倍数: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |x| x * 倍数)
}

// 创建更复杂的闭包生成器
fn 创建累加器(初始值: i32) -> Box<dyn FnMut(i32) -> i32> {
    let mut 累积值 = 初始值;
    Box::new(move |增量| {
        累积值 += 增量;
        累积值
    })
}

fn main() {
    let 三倍乘法器 = 创建乘法器(3);
    let 五倍乘法器 = 创建乘法器(5);
    
    for i in 1..=5 {
        println!("{} × 3 = {}, {} × 5 = {}", 
                i, 三倍乘法器(i), i, 五倍乘法器(i));
    }
    
    let mut 累加器 = 创建累加器(0);
    
    for 增量 in 1..=5 {
        let 结果 = 累加器(增量);
        println!("累加器当前值: {}", 结果);
    }
}

🚀 实际应用场景

集合操作

rust
fn main() {
    let 学生成绩 = vec![
        ("张三", 85),
        ("李四", 92),
        ("王五", 78),
        ("赵六", 96),
        ("钱七", 88),
    ];
    
    // 过滤及格学生
    let 及格学生: Vec<_> = 学生成绩
        .iter()
        .filter(|(_, 成绩)| **成绩 >= 80)
        .collect();
    
    println!("及格学生:");
    for (姓名, 成绩) in 及格学生 {
        println!("{}: {}", 姓名, 成绩);
    }
    
    // 计算平均分
    let 平均分: f64 = 学生成绩
        .iter()
        .map(|(_, 成绩)| **成绩 as f64)
        .sum::<f64>() / 学生成绩.len() as f64;
    
    println!("平均分: {:.2}", 平均分);
    
    // 找到最高分学生
    let 最高分学生 = 学生成绩
        .iter()
        .max_by_key(|(_, 成绩)| *成绩);
    
    if let Some((姓名, 成绩)) = 最高分学生 {
        println!("最高分学生: {} ({}分)", 姓名, 成绩);
    }
}

事件处理系统

rust
use std::collections::HashMap;

// 简单的事件系统
struct 事件系统 {
    监听器: HashMap<String, Vec<Box<dyn Fn(&str)>>>,
}

impl 事件系统 {
    fn 新建() -> Self {
        事件系统 {
            监听器: HashMap::new(),
        }
    }
    
    fn 添加监听器<F>(&mut self, 事件名: &str, 回调: F)
    where
        F: Fn(&str) + 'static,
    {
        self.监听器
            .entry(事件名.to_string())
            .or_insert_with(Vec::new)
            .push(Box::new(回调));
    }
    
    fn 触发事件(&self, 事件名: &str, 数据: &str) {
        if let Some(回调列表) = self.监听器.get(事件名) {
            for 回调 in 回调列表 {
                回调(数据);
            }
        }
    }
}

fn main() {
    let mut 事件系统 = 事件系统::新建();
    
    // 添加用户登录事件监听器
    事件系统.添加监听器("用户登录", |用户名| {
        println!("📝 日志: 用户 {} 已登录", 用户名);
    });
    
    事件系统.添加监听器("用户登录", |用户名| {
        println!("📧 邮件: 发送欢迎邮件给 {}", 用户名);
    });
    
    // 模拟事件触发
    事件系统.触发事件("用户登录", "张三");
}

📚 总结

本教程全面介绍了 Rust 闭包的核心概念和实际应用:

主要内容回顾

  1. 闭包基础:理解闭包的概念、语法和类型推断
  2. 环境捕获:掌握三种捕获模式(Fn、FnMut、FnOnce)
  3. move 关键字:学会强制获取所有权的使用场景
  4. 高阶函数:接受和返回闭包的函数编程模式
  5. 实际应用:集合操作、事件处理等场景

关键概念总结

概念特点使用场景
Fn不可变借用环境多次调用的只读操作
FnMut可变借用环境需要修改捕获变量
FnOnce获取环境所有权只调用一次或消费捕获变量
move强制获取所有权线程间传递数据

闭包的优势

  • 灵活性:可以在需要时定义,无需单独命名
  • 环境捕获:可以访问外部变量,减少参数传递
  • 类型推断:减少显式类型注解
  • 内联优化:编译器可以更好地优化闭包

实践建议

  • 优先使用 Fn trait,需要时再使用 FnMutFnOnce
  • 在多线程环境中使用 move 关键字
  • 在集合操作中充分利用闭包的简洁性
  • 使用闭包实现事件驱动编程模式

注意事项

  • 注意闭包的生命周期和内存占用
  • 避免在闭包中捕获过多不必要的变量
  • 在高频调用场景中考虑性能影响
  • 复杂的闭包可能影响代码可读性

闭包是 Rust 函数式编程的重要特性,掌握闭包的使用将显著提高您的 Rust 编程效率和代码表达力。通过本教程的学习,您现在应该能够在实际项目中灵活使用闭包来解决各种编程问题。


继续学习下一章 - Rust 所有权

本站内容仅供学习和研究使用。