趣味学RUST基础篇(函数式编程闭包)

函数式编程:闭包和迭代器来啦!

Rust 这门语言可不是凭空蹦出来的,它可是"集百家之长"的混血高手!其中一个重要的灵感来源,就是听起来有点高冷的------函数式编程

别被名字吓到,其实它没那么玄乎。简单说,就是把函数玩出花来:比如让函数当"快递包裹"一样传来传去,既能塞进别的函数里当参数,也能被存起来等会儿再用,甚至还能让一个函数"生"出另一个函数......是不是有点像编程界的变形金刚?

咱们这章不搞学术辩论,不纠结"到底啥才算函数式编程"。咱们要干点更实在的:带你认识 Rust 里两个超实用、又带点函数式范儿的明星功能------

闭包(Closures):一种可以"打包"代码的神奇结构,还能存进变量里,想啥时候用就啥时候用,灵活得不像话。

迭代器(Iterators):处理一连串数据的神器,比如遍历列表、过滤元素、转换数据......一行代码搞定复杂操作,简洁又高效。

最后,偷偷剧透一下性能表现:快!快到飞起! 别以为写得高级就一定慢,Rust 的闭包和迭代器可是"速度与激情"兼备,优化得那叫一个丝滑。

毕竟,在 Rust 的世界里,写得漂亮和跑得飞快,从来都不是单选题!

接下来,我们就来探讨一下闭包是怎么回事

闭包:Rust 里的"随身小助手"

我们可以把闭包想象成一个能随身携带、随时待命的"迷你程序员"------它不占地方,还能记住你给它的上下文信息,比如:"嘿,我现在在干嘛?"、"我手头有哪些数据?"。

闭包 = 匿名函数 + 记忆力超强的小助手

它不像普通函数那样需要正式的名字和一堆类型声明,它更像一个即插即用的"U盘程序",你可以把它存进变量、传给别的函数,甚至带到别的线程去执行。最关键的是:它能"记住"自己被创建时周围的环境

举个栗子:T恤公司送福利!

假设你开了一家限量版T恤公司,搞了个抽奖活动:抽中的用户,如果填了喜欢的颜色,就送那个颜色如果没填?那就送店里最多的一款!

我们用代码来实现这个逻辑:

rust 复制代码
#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
    Red,
    Blue,
}

struct Inventory {
    shirts: Vec<ShirtColor>,
}

impl Inventory {
    fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
        user_preference.unwrap_or_else(|| self.most_stocked())
    }

    fn most_stocked(&self) -> ShirtColor {
        let mut num_red = 0;
        let mut num_blue = 0;

        for color in &self.shirts {
            match color {
                ShirtColor::Red => num_red += 1,
                ShirtColor::Blue => num_blue += 1,
            }
        }
        if num_red > num_blue {
            ShirtColor::Red
        } else {
            ShirtColor::Blue
        }
    }
}

fn main() {
    let store = Inventory {
        shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
    };

    let user_pref1 = Some(ShirtColor::Red);
    let giveaway1 = store.giveaway(user_pref1);
    println!(
        "The user with preference {:?} gets {:?}",
        user_pref1, giveaway1
    );

    let user_pref2 = None;
    let giveaway2 = store.giveaway(user_pref2);
    println!(
        "The user with preference {:?} gets {:?}",
        user_pref2, giveaway2
    );
}

库存里有几件T恤,比如:蓝、红、蓝。现在,我们重点关注 giveaway 方法,决定送哪件。

关键来了------我们这样写:

rust 复制代码
fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
    user_preference.unwrap_or_else(|| self.most_stocked())
}

注意这个 || self.most_stocked() ------这就是一个闭包

  • || 是它的"启动按钮"(没有参数)。
  • 它做的事是:调用 self.most_stocked(),也就是查库存最多的颜色。
  • 它"记得" self 是谁,因为它是在 Inventory 的方法里定义的!
为什么不用普通函数?

因为普通函数不知道 self 是谁啊!但闭包不一样,它像个小跟班,诞生那一刻就记住了周围的环境:"哦,原来 self 是这个库存对象!"

所以,当用户没选颜色(None)时,unwrap_or_else 就会"启动"这个闭包,去查库存最多的颜色------蓝的多,那就送蓝色!

运行结果:

复制代码
The user with preference Some(Red) gets Red
The user with preference None gets Blue

完美!闭包在这里干了一件很自然的事:按需执行 + 记住上下文


闭包的"智商":类型推断

闭包很聪明,它通常不需要你告诉它参数和返回值的类型。Rust 编译器会"看眼色"自动推断。

比如这两个,效果一样:

rust 复制代码
let add_one_v1 = |x: u32| -> u32 { x + 1 }; // 明确标注
let add_one_v2 = |x|             x + 1  ;   // 完全靠猜,也能对!

是不是很像你写 let x = 5;,Rust 自动知道 xi32

但闭包也有"认死理"的时候------一旦它"学会"了某种类型,就再也学不会别的了

看这个例子:

rust 复制代码
let example_closure = |x| x;

let s = example_closure(String::from("hello")); // 第一次:String
let n = example_closure(5);                     // 第二次:整数?错!

编译器会怒吼:

错了!你第一次让它处理 String,它就认定这辈子只处理 String!你现在塞个 5 给它?门都没有!

所以:闭包的类型在第一次调用时就被"锁定"了


闭包的三种"拿数据"方式

闭包不是小偷,但它确实会"拿"环境里的数据。怎么拿?有三种风格:

方式 类比 说明
不可变借用 "我只看看,不动" 多个闭包可以同时看同一个数据
可变借用 "我可以改,但得排队" 只能有一个闭包在改
move "这东西我搬走了,归我了!" 数据所有权转移,原地清空
示例1:只看看(不可变借用)
rust 复制代码
let list = vec![1, 2, 3];
let print_it = || println!("列表是:{:?}", list);

print_it(); // 输出 [1, 2, 3]
println!("外面还能用:{:?}", list); // 没问题!
示例2:动手改(可变借用)
rust 复制代码
let mut list = vec![1, 2, 3];
let mut add_it = || list.push(7);

add_it(); // 加个7
println!("{:?}", list); // [1, 2, 3, 7]

注意:这时候你不能在 add_it 定义后、调用前打印 list,因为可变借用"锁"住了它!

示例3:直接搬走(move)

最狠的一招!尤其是跨线程时必须用:

rust 复制代码
use std::thread;

let list = vec![1, 2, 3];
thread::spawn(move || {
    println!("我在新线程里,拿着你的list:{:?}", list);
}).join().unwrap();

加了 move,闭包就把 list 的所有权抢走了,主线程不能再用它。

如果不加 move?编译器会警告:"你这引用可能变成悬空指针,太危险!"

闭包的"身份证":Fn、FnMut、FnOnce

Rust 给闭包发了三种"身份证",用来决定它能干啥:

身份证 能干啥 例子
Fn 只读环境,不改也不拿 ` println!("hello")`
FnMut 可以修改环境 ` list.push(1)`
FnOnce 只能用一次,因为拿走了东西 ` drop(some_string)`

小贴士:这三个是"包含关系"------FnOnce 最基础,FnMut 更强,Fn 最"干净"。

举个真实场景:sort_by_key

你想按矩形的宽度排序:

rust 复制代码
list.sort_by_key(|r| r.width);

这个闭包只读了 r.width,没动任何环境变量,所以它是 Fn,可以被调用多次(每个元素都调一次)。

但如果你这么写:

rust 复制代码
let mut ops = vec![];
let msg = String::from("called");

list.sort_by_key(|r| {
    ops.push(msg); //  错!msg被拿走了!
    r.width
});

这就变成了 FnOnce------只能调用一次,因为 msg 被"搬走"了。

sort_by_key 需要能调用多次的闭包(FnMutFn),所以编译器直接拒绝:

cannot move out of captured variable in an FnMut closure

解决办法?简单:msg.clone(),每人送一个副本,皆大欢喜!


总结:闭包的三大魅力

魅力 说明
匿名灵活 不用命名,随手创建,随手用
记忆超群 能捕获定义时的环境变量
类型智能 编译器自动推断,省心省力

下一站:迭代器!

闭包常和迭代器搭档,比如:

rust 复制代码
let expensive: Vec<i32> = items
    .iter()
    .map(|x| x * 2)           // 闭包处理每个元素
    .filter(|x| x > &10)      // 闭包过滤
    .collect();

是不是很像流水线?每个环节都是一个闭包,串起来处理数据,既优雅又高效!

所以记住:

闭包不是函数的替代品,而是 Rust 给你的一把"瑞士军刀"------小巧、锋利、随用随取。

相关推荐
草履虫建模2 小时前
在 RuoYi 中接入 3D「园区驾驶舱」:Vue2 + Three.js + Nginx
运维·开发语言·javascript·spring boot·nginx·spring cloud·微服务
MC皮蛋侠客2 小时前
使用python test测试http接口
开发语言·python·http
Want5953 小时前
C/C++圣诞树②
c语言·c++·算法
胡耀超3 小时前
5、Python-NumPy科学计算基础
开发语言·人工智能·python·深度学习·numpy
点灯小铭3 小时前
基于MATLAB的车牌识别系统
开发语言·单片机·数码相机·matlab·毕业设计·课程设计
十八旬3 小时前
苍穹外卖项目实战(day7-2)-购物车操作功能完善-记录实战教程、问题的解决方法以及完整代码
java·开发语言·windows·spring boot·mysql
BIGSHU09233 小时前
java多线程场景3-并发处理和异步请求
java·开发语言·python
索迪迈科技3 小时前
算法题(203):矩阵最小路径和
线性代数·算法·矩阵
默默无名的大学生4 小时前
数据结构——链表的基本操作
数据结构·算法