首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >16-Rust 教程 - 闭包

16-Rust 教程 - 闭包

作者头像
LarryLan
发布2026-04-28 12:25:18
发布2026-04-28 12:25:18
590
举报

闭包

匿名函数的艺术:不用取名,照样能干活

🎬 引入

你有没有遇到过这种情况:想写个小函数,就为了用那么一次,结果还得正儿八经地给它取个名字、定义参数、写返回值……感觉就像为了拧个螺丝钉,专门跑去买了个电钻。

在 Rust 里,有个好东西叫闭包(Closure),让你能随手写个"一次性函数",不用取名,随写随用。今天咱们就聊聊这个让代码更简洁的神器。

📌 核心概念

什么是闭包?

闭包,说白了就是匿名函数。它不需要名字,可以直接写在需要它的地方。

生活化类比:

想象你去餐厅点餐:

  • 普通函数:就像菜单上的菜品,有名字("宫保鸡丁"),谁都能点
  • 闭包:就像你跟厨师说"少放辣、多放葱",这个要求只有这一次有效,不需要给这个要求取名字

闭包的语法

代码语言:javascript
复制
// 最简单的闭包
let add = |x, y| x + y;

// 调用方式
let result = add(, );  // 8

看到了吗?|x, y| 这部分是参数列表,后面的 x + y 是函数体。就这么简单!

闭包的三种形式

Rust 的闭包有三种"性格",取决于它怎么使用环境变量:

类型

语法

特点

Fn

`

FnMut

`

FnOnce

`

生活化类比:

  • Fn:借书看——书还是图书馆的,你看完了别人还能看
  • FnMut:借车开——车可以开走,还能还回来继续借
  • FnOnce:吃蛋糕——吃完就没了,只能享受一次

💻 代码示例

基础示例:排序用闭包

代码语言:javascript
复制
fn main() {
    let mut numbers = vec![, , , , ];
    
    // 用闭包定义排序规则:从大到小
    numbers.sort_by(|a, b| b.cmp(a));
    
    println!("{:?}", numbers);  // [9, 8, 5, 2, 1]
}

看到没?|a, b| b.cmp(a) 这个闭包直接写在 sort_by 里面,不用单独定义函数,多清爽!

捕获环境变量的闭包

代码语言:javascript
复制
fn main() {
    let x = ;
    
    // 闭包可以"捕获"外部的 x
    let add_x = |n| n + x;
    
    println!("{}", add_x());  // 50
}

这个闭包"记住"了外面的 x = 42,每次调用都加上这个值。

错误示例:闭包的捕获规则

代码语言:javascript
复制
fn main() {
    let mut count = ;
    
    // 这个闭包要修改 count
    let mut increment = || count += ;
    
    increment();  // ✅ 可以
    increment();  // ✅ 可以
    increment();  // ✅ 可以
    
    println!("count = {}", count);  // ❌ 编译错误!
    // 错误信息:cannot borrow `count` as immutable 
    // because it is also borrowed as mutable
}

编译器在说什么人话?

编译器说:"你那个闭包已经可变借用了 count,现在你又想不可变借用它来打印,不行!"

修复方法:

代码语言:javascript
复制
fn main() {
    let mut count = ;
    
    {
        let mut increment = || count += ;
        increment();
        increment();
        increment();
    }  // 闭包的作用域结束,借用释放
    
    println!("count = {}", count);  // ✅ 现在可以了
}

移动闭包(move)

有时候你想让闭包"拥有"变量,而不是借用:

代码语言:javascript
复制
fn main() {
    let name = String::from("Rust");
    
    // 用 move 让闭包拥有 name
    let greet = move || {
        println!("Hello, {}!", name);
    };
    
    greet();  // Hello, Rust!
    // greet();  // ❌ 第二次调用会失败,name 被移动了
}

生活化类比:

  • 普通闭包:借朋友的相机拍照,拍完要还
  • move 闭包:直接把相机买下来,想怎么用就怎么用,但朋友就不能用了

🐛 常见坑点

坑点 1:闭包的类型推断

代码语言:javascript
复制
fn main() {
    let add = |x, y| x + y;
    
    let result1 = add(, );      // ✅ i32
    let result2 = add(1.0, 2.0);  // ❌ 编译错误!
}

编译器在说什么?

"你第一次调用用的是 i32,所以这个闭包的类型就固定为 i32 了,不能再用 f64 调用!"

解决方案:

显式指定类型:

代码语言:javascript
复制
let add = |x: f64, y: f64| x + y;

坑点 2:闭包作为函数参数

代码语言:javascript
复制
fn main() {
    let numbers = vec![, , , , ];
    
    // ❌ 这样写不行
    fn process(nums: Vec<i32>, f: |i32| -> i32) {
        // ...
    }
}

正确写法:

代码语言:javascript
复制
// 用 Fn trait 作为参数类型
fn process<F>(nums: Vec<i32>, f: F) 
where
    F: Fn(i32) -> i32
{
    for n in nums {
        println!("{}", f(n));
    }
}

fn main() {
    let numbers = vec![, , , , ];
    process(numbers, |x| x * );
}

坑点 3:返回闭包

代码语言:javascript
复制
// ❌ 这样写不行
fn make_adder(x: i32) -> |i32| -> i32 {
    |y| x + y
}

正确写法:

代码语言:javascript
复制
// 用 impl Trait
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

// 或者用 Box(需要堆分配)
fn make_adder_boxed(x: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |y| x + y)
}

🎯 实战案例

案例 1:数据过滤和转换

代码语言:javascript
复制
fn main() {
    let numbers = vec![, , , , , , , , , ];
    
    // 链式调用:过滤偶数 -> 平方 -> 收集
    let result: Vec<i32> = numbers
        .iter()
        .filter(|&n| n %  == )      // 闭包 1:过滤
        .map(|&n| n * n)              // 闭包 2:转换
        .collect();
    
    println!("{:?}", result);  // [4, 16, 36, 64, 100]
}

看到没?两个闭包链式调用,代码像流水线一样清晰!

案例 2:自定义排序规则

代码语言:javascript
复制
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let mut people = vec![
        Person { name: String::from("Alice"), age:  },
        Person { name: String::from("Bob"), age:  },
        Person { name: String::from("Charlie"), age:  },
    ];
    
    // 按年龄排序
    people.sort_by(|a, b| a.age.cmp(&b.age));
    
    // 按名字长度排序
    people.sort_by(|a, b| a.name.len().cmp(&b.name.len()));
    
    for p in &people {
        println!("{}: {}", p.name, p.age);
    }
}

案例 3:延迟计算

代码语言:javascript
复制
fn main() {
    let expensive_calculation = || {
        println!("正在计算...");
        // 模拟耗时操作
        std::thread::sleep(std::time::Duration::from_secs());
        
    };
    
    // 闭包还没执行
    println!("准备调用闭包...");
    
    // 现在才执行
    let result = expensive_calculation();
    println!("结果:{}", result);
}

闭包可以延迟执行,等到真正需要结果的时候才计算。

🧠 思维导图

16-闭包
16-闭包

📝 小结

金句回顾:

  1. 闭包就是匿名函数——不用取名,随写随用
  2. 三种性格:Fn(只读)、FnMut(可变)、FnOnce(一次)
  3. move 关键字——让闭包拥有变量,而不是借用
  4. 类型推断——第一次调用就固定了类型
  5. 链式调用——闭包 + 迭代器,代码如流水

下篇预告:

闭包和迭代器是绝配!下篇咱们深入聊聊迭代器,看看怎么用闭包把数据处理玩出花来。你会爱上 iter().map().filter().collect() 这个套路的!

🔗 参考资料

  • Rust Book - 闭包
  • Rust by Example - Closures
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Larry的Hub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 闭包
    • 🎬 引入
    • 📌 核心概念
      • 什么是闭包?
      • 闭包的语法
      • 闭包的三种形式
    • 💻 代码示例
      • 基础示例:排序用闭包
      • 捕获环境变量的闭包
      • 错误示例:闭包的捕获规则
      • 移动闭包(move)
    • 🐛 常见坑点
      • 坑点 1:闭包的类型推断
      • 坑点 2:闭包作为函数参数
      • 坑点 3:返回闭包
    • 🎯 实战案例
      • 案例 1:数据过滤和转换
      • 案例 2:自定义排序规则
      • 案例 3:延迟计算
    • 🧠 思维导图
    • 📝 小结
    • 🔗 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档