函数式编程:闭包和迭代器来啦!
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 自动知道 x
是 i32
?
但闭包也有"认死理"的时候------一旦它"学会"了某种类型,就再也学不会别的了!
看这个例子:
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
需要能调用多次的闭包(FnMut
或 Fn
),所以编译器直接拒绝:
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 给你的一把"瑞士军刀"------小巧、锋利、随用随取。