rust学习-闭包
- [1. 闭包基础](#1. 闭包基础)
- [2. 闭包语法详解](#2. 闭包语法详解)
- [3. 闭包的三种类型](#3. 闭包的三种类型)
-
- [3.1 Fn - 不可变借用](#3.1 Fn - 不可变借用)
- [3.2 FnMut - 可变借用](#3.2 FnMut - 可变借用)
- [3.3 FnOnce - 获取所有权](#3.3 FnOnce - 获取所有权)
- [4. 闭包捕获环境的方式](#4. 闭包捕获环境的方式)
-
- [4.1 不可变借用(默认)](#4.1 不可变借用(默认))
- [4.2 可变借用](#4.2 可变借用)
- [4.3 获取所有权(move)](#4.3 获取所有权(move))
- [4.4 捕获多个变量的混合方式](#4.4 捕获多个变量的混合方式)
- [5. 闭包作为函数参数](#5. 闭包作为函数参数)
-
- [5.1 泛型参数](#5.1 泛型参数)
- [5.2 trait 对象(动态分发)](#5.2 trait 对象(动态分发))
- [5.3 接受不同类型的闭包](#5.3 接受不同类型的闭包)
- [6. 闭包作为返回值](#6. 闭包作为返回值)
-
- [6.1 返回闭包的挑战](#6.1 返回闭包的挑战)
- [6.2 返回不同类型的闭包](#6.2 返回不同类型的闭包)
- [6.3 闭包工厂模式](#6.3 闭包工厂模式)
- [7. 闭包与迭代器](#7. 闭包与迭代器)
-
- [7.1 常用迭代器方法](#7.1 常用迭代器方法)
- [7.2 自定义迭代器适配器](#7.2 自定义迭代器适配器)
- [8. 闭包与并发](#8. 闭包与并发)
-
- [8.1 线程中的闭包](#8.1 线程中的闭包)
- [8.2 通道与闭包](#8.2 通道与闭包)
- [8.3 闭包与原子类型](#8.3 闭包与原子类型)
- [9. 实际应用示例](#9. 实际应用示例)
-
- [9.1 配置化算法](#9.1 配置化算法)
- [9.2 事件处理系统](#9.2 事件处理系统)
- [9.3 中间件系统](#9.3 中间件系统)
- [10. 性能考虑](#10. 性能考虑)
-
- [10.1 闭包 vs 函数指针](#10.1 闭包 vs 函数指针)
- [10.2 内联优化](#10.2 内联优化)
- [10.3 闭包大小与内存布局](#10.3 闭包大小与内存布局)
闭包是 Rust 中强大且灵活的特性,可以用于创建匿名函数并捕获环境中的变量。闭包在很多场景下都非常有用,比如迭代器、回调函数、并发编程等。
1. 闭包基础
什么是闭包?
闭包是可以捕获其所在环境的匿名函数。它们可以存储为变量、作为参数传递或从函数返回。
基本示例
rust
fn main() {
// 定义一个闭包
let add_one = |x: i32| -> i32 { x + 1 };
// 调用闭包
let result = add_one(5);
println!("5 + 1 = {}", result); // 输出: 6
// 闭包可以捕获环境变量
let y = 10;
let add_y = |x| x + y;
println!("5 + y = {}", add_y(5)); // 输出: 15
}
2. 闭包语法详解
完整语法 vs 简化语法
rust
fn main() {
// 1. 完整语法(显式类型注解)
let closure1 = |x: i32, y: i32| -> i32 {
let result = x + y;
result * 2
};
// 2. 简化语法(类型推断)
let closure2 = |x, y| x + y;
// 3. 无参数闭包
let get_answer = || 42;
// 4. 单表达式闭包(省略花括号)
let square = |x: i32| x * x;
println!("closure1: {}", closure1(3, 4)); // 14
println!("closure2: {}", closure2(3, 4)); // 7
println!("get_answer: {}", get_answer()); // 42
println!("square: {}", square(5)); // 25
}
闭包参数的特殊语法
rust
fn main() {
// 闭包可以指定引用类型作为参数
let print_len = |s: &str| s.len();
println!("长度: {}", print_len("hello")); // 5
// 闭包可以有可变参数
let mut counter = 0;
let mut increment = || {
counter += 1;
println!("计数器: {}", counter);
};
increment(); // 计数器: 1
increment(); // 计数器: 2
// 闭包参数也可以有生命周期
let get_first_char = |s: &str| -> Option<char> {
s.chars().next()
};
println!("首字符: {:?}", get_first_char("Rust")); // Some('R')
}
3. 闭包的三种类型
Rust 闭包实现了三个 trait 中的一个或多个:Fn、FnMut、FnOnce。
3.1 Fn - 不可变借用
rust
fn main() {
let x = 10;
// 这个闭包实现了 Fn,因为它以不可变方式借用 x
let print_x = || println!("x = {}", x);
print_x(); // x = 10
print_x(); // x = 10(可以多次调用)
// x 仍然可以访问
println!("x 仍然是 {}", x); // x 仍然是 10
// 传递 Fn 闭包
fn call_twice<F: Fn()>(f: F) {
f();
f();
}
call_twice(print_x); // 打印两次 x = 10
}
3.2 FnMut - 可变借用
rust
fn main() {
let mut counter = 0;
// 这个闭包实现了 FnMut,因为它以可变方式借用 counter
let mut increment = || {
counter += 1;
println!("计数器: {}", counter);
};
increment(); // 计数器: 1
increment(); // 计数器: 2
// 注意:在可变借用活跃期间,不能访问 counter
// println!("{}", counter); // 错误!
// 但闭包调用结束后可以访问
println!("最终值: {}", counter); // 最终值: 2
// 传递 FnMut 闭包
fn call_with_mut<F: FnMut()>(mut f: F) {
f();
f();
}
let mut another_counter = 0;
call_with_mut(|| {
another_counter += 1;
println!("另一个计数器: {}", another_counter);
});
}
3.3 FnOnce - 获取所有权
rust
fn main() {
let expensive_data = vec![1, 2, 3, 4, 5];
// 这个闭包实现了 FnOnce,因为它获取了 expensive_data 的所有权
let consume_data = || {
println!("消耗数据: {:?}", expensive_data);
// expensive_data 在这里被丢弃
};
consume_data(); // 消耗数据: [1, 2, 3, 4, 5]
// consume_data(); // 错误!闭包只能调用一次,因为 expensive_data 已被移动
// 使用 move 关键字的例子
let data = String::from("重要数据");
let take_ownership = move || {
println!("获取所有权: {}", data);
};
take_ownership(); // 获取所有权: 重要数据
// println!("{}", data); // 错误!data 的所有权已转移
}
类型之间的层级关系
rust
fn main() {
// Fn 是 FnMut 的子集,FnMut 是 FnOnce 的子集
// 这意味着:
// - 任何实现了 Fn 的闭包也实现了 FnMut 和 FnOnce
// - 任何实现了 FnMut 的闭包也实现了 FnOnce
// 示例:接受不同类型的闭包参数
fn accept_fn<F: Fn()>(f: F) {
f();
}
fn accept_fn_mut<F: FnMut()>(mut f: F) {
f();
}
fn accept_fn_once<F: FnOnce()>(f: F) {
f();
}
let immutable_closure = || println!("不可变闭包");
let mut mutable_counter = 0;
let mut mutable_closure = || {
mutable_counter += 1;
println!("可变闭包: {}", mutable_counter);
};
let expensive_string = String::from("数据");
let once_closure = move || {
println!("一次性闭包: {}", expensive_string);
};
// Fn 闭包可以传递给所有三个函数
accept_fn(immutable_closure);
accept_fn_mut(immutable_closure);
accept_fn_once(immutable_closure);
// FnMut 闭包可以传递给 FnMut 和 FnOnce
accept_fn_mut(mutable_closure);
accept_fn_once(mutable_closure);
// FnOnce 闭包只能传递给 FnOnce
accept_fn_once(once_closure);
}
4. 闭包捕获环境的方式
4.1 不可变借用(默认)
rust
fn main() {
let x = 5;
let y = 10;
// 闭包不可变借用 x 和 y
let sum = || x + y;
println!("和: {}", sum()); // 15
println!("x: {}, y: {}", x, y); // x: 5, y: 10(仍然可访问)
// 多个闭包可以同时不可变借用
let product = || x * y;
println!("积: {}", product()); // 50
}
4.2 可变借用
rust
fn main() {
let mut counter = 0;
// 闭包可变借用 counter
let mut increment = || {
counter += 1;
println!("计数: {}", counter);
};
increment(); // 计数: 1
increment(); // 计数: 2
// 在此期间不能有其他引用
// println!("{}", counter); // 错误!
// 闭包调用结束后可以访问
println!("最终计数: {}", counter); // 最终计数: 2
}
4.3 获取所有权(move)
rust
fn main() {
let data = vec![1, 2, 3, 4, 5];
// 使用 move 强制闭包获取 data 的所有权
let closure = move || {
println!("数据长度: {}", data.len());
// data 在这里被丢弃
};
closure(); // 数据长度: 5
// println!("{:?}", data); // 错误!data 的所有权已转移
// move 在并发编程中特别有用
let message = String::from("来自主线程的消息");
std::thread::spawn(move || {
println!("线程收到: {}", message);
}).join().unwrap();
}
4.4 捕获多个变量的混合方式
rust
fn main() {
let x = 10; // i32,实现了 Copy
let mut y = 20; // 可变 i32
let data = String::from("数据"); // String,没有实现 Copy
let complex_closure = || {
// x: 不可变借用(因为只是读取)
println!("x = {}", x);
// y: 可变借用(因为要修改)
y += x;
println!("y 现在是 {}", y);
// data: 移动(使用 move 关键字时)
// 但这里我们只是读取,所以默认是不可变借用
println!("data = {}", data);
// 如果我们想获取 data 的所有权,需要明确使用 move
};
complex_closure();
println!("外部: x = {}, y = {}", x, y); // x 和 y 仍然可用
println!("外部: data = {}", data); // data 仍然可用
}
5. 闭包作为函数参数
5.1 泛型参数
rust
// 使用泛型和 trait 约束
fn apply<F>(value: i32, f: F) -> i32
where
F: Fn(i32) -> i32,
{
f(value)
}
fn main() {
let double = |x| x * 2;
let square = |x| x * x;
println!("apply(5, double) = {}", apply(5, double)); // 10
println!("apply(5, square) = {}", apply(5, square)); // 25
// 捕获环境的闭包也可以传递
let add_y = |x| {
let y = 10;
x + y
};
println!("apply(5, add_y) = {}", apply(5, add_y)); // 15
}
5.2 trait 对象(动态分发)
rust
// 使用 Box<dyn Trait> 进行动态分发
fn process_numbers(numbers: &[i32], processor: Box<dyn Fn(i32) -> i32>) -> Vec<i32> {
numbers.iter().map(|&n| processor(n)).collect()
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 传递不同的处理器
let doubled = process_numbers(&numbers, Box::new(|x| x * 2));
println!("翻倍: {:?}", doubled); // [2, 4, 6, 8, 10]
let incremented = process_numbers(&numbers, Box::new(|x| x + 10));
println!("加10: {:?}", incremented); // [11, 12, 13, 14, 15]
}
5.3 接受不同类型的闭包
rust
// 接受 Fn、FnMut 或 FnOnce 闭包
fn execute_closure<F>(f: F)
where
F: FnOnce(),
{
f();
}
// 更灵活:可以接受返回值的闭包
fn transform<F, T, R>(value: T, f: F) -> R
where
F: FnOnce(T) -> R,
{
f(value)
}
fn main() {
// Fn 闭包
execute_closure(|| println!("你好!"));
// FnMut 闭包
let mut count = 0;
execute_closure(|| {
count += 1;
println!("计数: {}", count);
});
// FnOnce 闭包
let data = String::from("数据");
execute_closure(move || {
println!("消耗: {}", data);
});
// 带返回值的闭包
let result = transform(5, |x| x * 3);
println!("结果: {}", result); // 15
}
6. 闭包作为返回值
6.1 返回闭包的挑战
rust
// 简单情况:返回没有捕获环境的闭包
fn create_adder() -> impl Fn(i32, i32) -> i32 {
|x, y| x + y
}
// 复杂情况:返回捕获了环境的闭包
fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
// 必须使用 move,因为 factor 需要移动到闭包中
move |x| x * factor
}
fn main() {
let adder = create_adder();
println!("3 + 4 = {}", adder(3, 4)); // 7
let double = make_multiplier(2);
println!("5 * 2 = {}", double(5)); // 10
let triple = make_multiplier(3);
println!("5 * 3 = {}", triple(5)); // 15
}
6.2 返回不同类型的闭包
rust
// 使用 Box<dyn Trait> 返回不同类型的闭包
enum Operation {
Add,
Subtract,
Multiply,
}
fn get_operation(op: Operation) -> Box<dyn Fn(i32, i32) -> i32> {
match op {
Operation::Add => Box::new(|x, y| x + y),
Operation::Subtract => Box::new(|x, y| x - y),
Operation::Multiply => Box::new(|x, y| x * y),
}
}
fn main() {
let add = get_operation(Operation::Add);
let subtract = get_operation(Operation::Subtract);
let multiply = get_operation(Operation::Multiply);
println!("10 + 5 = {}", add(10, 5)); // 15
println!("10 - 5 = {}", subtract(10, 5)); // 5
println!("10 * 5 = {}", multiply(10, 5)); // 50
}
6.3 闭包工厂模式
rust
// 创建配置化的闭包
fn create_logger(level: &'static str) -> impl Fn(&str) {
move |message| println!("[{}] {}", level, message)
}
// 创建有状态的闭包
fn create_counter() -> impl Fn() -> i32 {
let mut count = 0;
move || {
count += 1;
count
}
}
fn main() {
// 日志记录器工厂
let info_log = create_logger("INFO");
let error_log = create_logger("ERROR");
info_log("应用启动");
error_log("发生错误");
// 计数器工厂
let mut counter1 = create_counter();
let mut counter2 = create_counter();
println!("计数器1: {}", counter1()); // 1
println!("计数器1: {}", counter1()); // 2
println!("计数器2: {}", counter2()); // 1(独立的计数器)
}
7. 闭包与迭代器
7.1 常用迭代器方法
rust
fn main() {
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// map: 转换每个元素
let doubled: Vec<i32> = numbers.iter().map(|&x| x * 2).collect();
println!("翻倍: {:?}", doubled); // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
// filter: 过滤元素
let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();
println!("偶数: {:?}", evens); // [2, 4, 6, 8, 10]
// filter_map: 过滤和转换
let maybe_strings = vec!["1", "two", "3", "four", "5"];
let numbers: Vec<i32> = maybe_strings
.iter()
.filter_map(|s| s.parse().ok())
.collect();
println!("解析的数字: {:?}", numbers); // [1, 3, 5]
// fold: 累积
let sum = numbers.iter().fold(0, |acc, &x| acc + x);
println!("总和: {}", sum); // 15
// reduce: 类似 fold,但使用第一个元素作为初始值
let product = numbers.iter().copied().reduce(|acc, x| acc * x);
println!("乘积: {:?}", product); // Some(15)
// any 和 all: 条件检查
let has_even = numbers.iter().any(|&x| x % 2 == 0);
let all_positive = numbers.iter().all(|&x| x > 0);
println!("有偶数吗? {}", has_even); // true
println!("都正数吗? {}", all_positive); // true
}
7.2 自定义迭代器适配器
rust
struct WindowIterator<'a, T> {
data: &'a [T],
window_size: usize,
index: usize,
}
impl<'a, T> Iterator for WindowIterator<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
if self.index + self.window_size <= self.data.len() {
let window = &self.data[self.index..self.index + self.window_size];
self.index += 1;
Some(window)
} else {
None
}
}
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
let window_iter = WindowIterator {
data: &data,
window_size: 3,
index: 0,
};
// 使用闭包处理窗口
let window_sums: Vec<i32> = window_iter
.map(|window| window.iter().sum())
.collect();
println!("窗口和: {:?}", window_sums); // [6, 9, 12]
}
8. 闭包与并发
8.1 线程中的闭包
rust
use std::thread;
use std::time::Duration;
fn main() {
// 基本线程闭包
let handle = thread::spawn(|| {
for i in 1..5 {
println!("线程: 计数 {}", i);
thread::sleep(Duration::from_millis(100));
}
});
// 主线程工作
for i in 1..3 {
println!("主线程: 计数 {}", i);
thread::sleep(Duration::from_millis(200));
}
handle.join().unwrap();
// 捕获数据的线程
let data = vec![1, 2, 3, 4, 5];
let handle = thread::spawn(move || {
println!("线程中的数据: {:?}", data);
// 计算总和
let sum: i32 = data.iter().sum();
sum
});
let result = handle.join().unwrap();
println!("线程计算结果: {}", result); // 15
}
8.2 通道与闭包
rust
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
// 生成工作线程
for i in 0..5 {
let tx_clone = tx.clone();
thread::spawn(move || {
// 模拟一些工作
let result = i * i;
// 发送结果
tx_clone.send((i, result)).unwrap();
});
}
// 丢弃原始的发送者,这样接收者知道不会有更多数据
drop(tx);
// 收集所有结果
let results: Vec<(i32, i32)> = rx.iter().collect();
println!("结果: {:?}", results);
}
8.3 闭包与原子类型
rust
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let counter = Arc::new(AtomicI32::new(0));
let mut handles = vec![];
// 创建10个线程,每个线程增加计数器1000次
for _ in 0..10 {
let counter_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
for _ in 0..1000 {
counter_clone.fetch_add(1, Ordering::SeqCst);
}
});
handles.push(handle);
}
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
println!("最终计数: {}", counter.load(Ordering::SeqCst)); // 10000
}
9. 实际应用示例
9.1 配置化算法
rust
// 策略模式使用闭包
struct DataProcessor<F> {
processor: F,
}
impl<F> DataProcessor<F>
where
F: Fn(&[i32]) -> i32,
{
fn new(processor: F) -> Self {
DataProcessor { processor }
}
fn process(&self, data: &[i32]) -> i32 {
(self.processor)(data)
}
}
fn main() {
// 不同的处理策略
let sum_processor = DataProcessor::new(|data| data.iter().sum());
let avg_processor = DataProcessor::new(|data| {
if data.is_empty() {
0
} else {
data.iter().sum::<i32>() / data.len() as i32
}
});
let data = vec![1, 2, 3, 4, 5];
println!("总和: {}", sum_processor.process(&data)); // 15
println!("平均值: {}", avg_processor.process(&data)); // 3
}
9.2 事件处理系统
rust
struct EventEmitter {
listeners: Vec<Box<dyn Fn(&str)>>,
}
impl EventEmitter {
fn new() -> Self {
EventEmitter { listeners: vec![] }
}
fn add_listener<F>(&mut self, listener: F)
where
F: Fn(&str) + 'static,
{
self.listeners.push(Box::new(listener));
}
fn emit(&self, event: &str) {
for listener in &self.listeners {
listener(event);
}
}
}
fn main() {
let mut emitter = EventEmitter::new();
// 添加事件监听器
emitter.add_listener(|event| {
println!("监听器1: 收到事件 '{}'", event);
});
emitter.add_listener(|event| {
println!("监听器2: 处理事件 '{}'", event);
});
// 触发事件
emitter.emit("应用启动");
emitter.emit("用户登录");
}
9.3 中间件系统
rust
type Middleware = Box<dyn Fn(&mut Context) -> Result<(), String>>;
struct Context {
request: String,
response: String,
data: Vec<String>,
}
fn create_middleware_chain() -> Vec<Middleware> {
vec![
Box::new(|ctx: &mut Context| {
println!("中间件1: 处理请求 '{}'", ctx.request);
ctx.data.push("来自中间件1的数据".to_string());
Ok(())
}),
Box::new(|ctx: &mut Context| {
println!("中间件2: 验证请求");
if ctx.request.is_empty() {
return Err("空请求".to_string());
}
ctx.data.push("来自中间件2的数据".to_string());
Ok(())
}),
Box::new(|ctx: &mut Context| {
println!("中间件3: 生成响应");
ctx.response = format!("对 '{}' 的响应", ctx.request);
ctx.data.push("来自中间件3的数据".to_string());
Ok(())
}),
]
}
fn main() {
let middleware_chain = create_middleware_chain();
let mut context = Context {
request: "获取数据".to_string(),
response: String::new(),
data: vec![],
};
// 执行中间件链
for middleware in &middleware_chain {
if let Err(e) = middleware(&mut context) {
println!("错误: {}", e);
return;
}
}
println!("响应: {}", context.response);
println!("数据: {:?}", context.data);
}
10. 性能考虑
10.1 闭包 vs 函数指针
rust
// 闭包(无捕获)可以转换为函数指针
fn benchmark<F>(iterations: usize, f: F) -> std::time::Duration
where
F: Fn(),
{
let start = std::time::Instant::now();
for _ in 0..iterations {
f();
}
start.elapsed()
}
fn main() {
let iterations = 1_000_000;
// 函数指针
fn simple_function() {
// 什么都不做
}
// 闭包(无捕获)
let closure = || {};
// 测量性能
let func_time = benchmark(iterations, simple_function);
let closure_time = benchmark(iterations, closure);
println!("函数指针时间: {:?}", func_time);
println!("闭包时间: {:?}", closure_time);
// 两者性能通常非常接近
}
10.2 内联优化
rust
// 闭包通常可以被内联优化
fn process_with_closure<F>(data: &[i32], f: F) -> Vec<i32>
where
F: Fn(i32) -> i32,
{
data.iter().map(|&x| f(x)).collect()
}
fn main() {
let data = vec![1, 2, 3, 4, 5];
// 这个闭包很可能被内联
let result = process_with_closure(&data, |x| x * 2);
println!("结果: {:?}", result);
// 捕获环境的闭包可能不会被内联
let factor = 3;
let result2 = process_with_closure(&data, |x| x * factor);
println!("结果2: {:?}", result2);
}
10.3 闭包大小与内存布局
rust
use std::mem;
fn main() {
// 无捕获的闭包大小
let closure1 = || println!("无捕获");
println!("无捕获闭包大小: {} 字节", mem::size_of_val(&closure1));
// 捕获 i32 的闭包
let x = 42;
let closure2 = || println!("x = {}", x);
println!("捕获 i32 的闭包大小: {} 字节", mem::size_of_val(&closure2));
// 捕获 String 的闭包
let s = String::from("hello");
let closure3 = move || println!("s = {}", s);
println!("捕获 String 的闭包大小: {} 字节", mem::size_of_val(&closure3));
// 捕获多个变量的闭包
let a = 1;
let b = 2.0;
let c = String::from("three");
let closure4 = move || {
println!("a = {}, b = {}, c = {}", a, b, c);
};
println!("捕获多个变量的闭包大小: {} 字节", mem::size_of_val(&closure4));
}
闭包的核心要点
1. 语法灵活 :|参数| 表达式 或 |参数| { 代码块 }
2. 类型推断 :编译器通常能推断出参数和返回类型
3. 三种 trait:
- Fn:不可变借用,可多次调用
- FnMut:可变借用,可多次调用
- FnOnce:获取所有权,只能调用一次
4. 捕获环境:可以捕获外部变量,通过不可变借用、可变借用或移动
最佳实践
1. 优先使用不可变借用 :除非需要修改捕获的变量
2. 适当使用 move :当闭包需要比捕获的变量活得久时
3. 考虑性能 :无捕获的闭包可以转换为函数指针,性能更好
4. 注意生命周期:返回闭包时要考虑生命周期问题