Rust进阶[part7]_闭包

AI-摘要
sonia33 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
Rust进阶[part7]_闭包
SoniaChenRust进阶[part7]_闭包
闭包概述
- 闭包是一个可以捕获所在环境中的变量的匿名函数
- 在Rust中,闭包通过
||
符号定义,可以像普通函数一样调用,但和函数不同,闭包可以访问外部作用域的变量
特点
- 匿名性:没有函数名,通常作为表达式使用
- 捕获环境:能自动捕获定义所在作用域中的变量(无需显式声明)
- 类型推断:参数和返回值类型可由编译器自动推断,无需显式标注
- 灵活语法:语法简洁,可根据复杂度调整写法(单行可省略
{}
和return
) - 实现trait:编译器会为闭包自动实现
Fn
、FnMut
或FnOnce
trait,使其能作为参数传递
语法
基本语法:|参数列表| 代码块
(返回值由最后一行表达式决定)
// 无参数闭包
let hello = || println!("Hello, closure!");
hello(); // 调用闭包
// 单参数闭包(省略类型标注)
let square = |x| x * x;
println!("{}", square(5)); // 输出:25
// 多参数闭包(显式标注类型,可选)
let add = |a: i32, b: i32| -> i32 { a + b };
println!("{}", add(2, 3)); // 输出:5
// 多行闭包(需用{}包裹)
let complex_calc = |x: i32| {
let y = x * 2;
y + 3 // 隐式返回
};
println!("{}", complex_calc(4)); // 输出:11
使用场景
函数的参数
闭包常作为参数传递给函数(如迭代器方法、异步任务等),实现灵活的逻辑注入
// 1. 结合迭代器使用
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled); // 输出:[2, 4, 6, 8, 10]
// 2. 自定义接受闭包的函数
fn apply<F>(f: F, value: i32) -> i32
where
F: Fn(i32) -> i32 // 约束闭包类型
{
f(value)
}
let result = apply(|x| x * 3 + 2, 5);
println!("{}", result); // 输出:17
捕获环境变量
可以捕获并使用其定义所在环境中的变量,捕获方式由编译器根据使用场景自动推断
// 1. 按引用捕获(&T):适用于只读访问
let message = String::from("Hello");
let print_msg = || println!("{}", message); // 捕获message的引用
print_msg(); // 输出:Hello
println!("{}", message); // 仍可使用message(未转移所有权)
// 2. 按可变引用捕获(&mut T):适用于修改变量
let mut count = 0;
let mut increment = || {
count += 1; // 修改捕获的变量
println!("Count: {}", count);
};
increment(); // 输出:Count: 1
increment(); // 输出:Count: 2
// 3. 按值捕获(T):适用于需要转移所有权的场景
let name = String::from("Alice");
let take_name = || {
println!("Name: {}", name); // 捕获name的所有权
};
take_name();
// println!("{}", name); // 错误:name的所有权已被闭包捕获
闭包原理
-
自动实现的函数类型:编译器会为每个闭包自动实现
Fn
、FnMut
或FnOnce
中的一个或多个trait:FnOnce
:闭包消耗捕获的变量(所有权转移),因此只能调用一次FnMut
:闭包通过可变引用修改捕获的变量,可多次调用Fn
:闭包通过不可变引用访问捕获的变量,可多次调用(实现Fn
的闭包自动实现FnMut
和FnOnce
)
-
闭包的类型推断:
- 闭包的参数和返回值类型由编译器根据上下文自动推断
- 同一闭包的类型是唯一的(即使签名相同,不同闭包类型也不同)
- 若需存储闭包或作为返回值,需通过trait对象(如
Box<dyn Fn(...) -> ...>
)实现
-
生命周期与闭包:
- 捕获引用的闭包,其生命周期受限于所捕获变量的生命周期
- 若闭包作为返回值,需确保返回的闭包不捕获已销毁的变量引用
move关键字
在参数列表前使用move
关键字,可强制闭包取得它所使用的环境值的所有权(忽略默认的引用捕获)
// 基础示例:转移所有权
let x = vec![1, 2, 3];
let equal_to_x = move |y| y == x; // x的所有权被闭包捕获
// println!("{:?}", x); // 错误:x的所有权已转移
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
// 多线程场景:确保闭包拥有变量所有权(避免生命周期问题)
use std::thread;
let s = String::from("thread message");
thread::spawn(move || { // 必须用move转移s的所有权到子线程
println!("{}", s);
}).join().unwrap();
练习
- 实现一个闭包,计算两个数的乘积并加上一个外部定义的偏移量(如
offset = 10
) - 使用闭包作为
filter
方法的参数,从整数列表中筛选出偶数 - 用
move
关键字创建一个闭包,在新线程中打印捕获的字符串 - 编写一个函数,接受两个闭包参数(一个计算平方,一个计算立方),并返回两个闭包结果的和
示例答案参考:
// 练习1
let offset = 10;
let calc = |a: i32, b: i32| a * b + offset;
println!("{}", calc(3, 4)); // 输出:22(3*4+10)
// 练习2
let numbers = vec![1, 2, 3, 4, 5, 6];
let evens: Vec<_> = numbers.into_iter().filter(|x| x % 2 == 0).collect();
println!("{:?}", evens); // 输出:[2, 4, 6]
// 练习3
use std::thread;
let msg = String::from("hello from thread");
thread::spawn(move || {
println!("{}", msg);
}).join().unwrap();
// 练习4
fn combine<F1, F2>(f1: F1, f2: F2, x: i32) -> i32
where
F1: Fn(i32) -> i32,
F2: Fn(i32) -> i32,
{
f1(x) + f2(x)
}
let square = |n| n * n;
let cube = |n| n * n * n;
println!("{}", combine(square, cube, 2)); // 输出:4 + 8 = 12
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果