Day 16: 闭包
1. 闭包基础
Section titled “1. 闭包基础”闭包是匿名函数,可以捕获其定义环境中的变量。Rust的闭包语法简洁优雅:
fn main() { // 完整语法 let add = |x: i32, y: i32| -> i32 { x + y };
// 单表达式可以省略大括号 let square = |x: i32| x * x;
// 无参数闭包 let greeting = || { println!("Hello, Rust!"); };
// 调用闭包 println!("3 + 5 = {}", add(3, 5)); println!("4 的平方 = {}", square(4)); greeting();
// 直接调用 println!("结果: {}", (|x: i32| x * 2)(5));}2. 类型推导
Section titled “2. 类型推导”Rust编译器会为闭包推导参数和返回类型,但每个闭包有唯一的实际类型:
fn main() { // 编译器推导类型 let add = |x, y| x + y; // i32 + i32 -> i32 println!("{}", add(1, 2));
// 注意:不同闭包即使签名相同也是不同类型 let f1 = |x| x; let f2 = |x| x;
// 但函数签名可以使用闭包作为参数 let nums = vec![1, 2, 3, 4, 5]; let sum: i32 = nums.iter().map(|x| x * 2).sum(); println!("sum: {}", sum);}3. Fn / FnMut / FnOnce Trait
Section titled “3. Fn / FnMut / FnOnce Trait”Rust闭包实现了以下三个trait,表示不同的捕获能力:
fn main() { // FnOnce: 可以调用一次,会消耗捕获的变量 let s = String::from("hello"); let consume = || { println!("{}", s); // s 被消耗,无法再使用 // s 不能再被使用 }; consume(); // 第一次调用 // consume(); // 编译错误!FnOnce 只能调用一次
// FnMut: 可以多次调用,会可变借用捕获的变量 let mut count = 0; let mut increment = || { count += 1; println!("count = {}", count); }; increment(); increment(); increment(); println!("最终 count = {}", count);
// Fn: 可以多次调用,只读借用捕获的变量 let nums = vec![1, 2, 3]; let print_nums = || { for n in &nums { print!("{} ", n); } println!(); }; print_nums(); print_nums(); // 可以多次调用}4. 捕获环境的方式
Section titled “4. 捕获环境的方式”闭包以三种方式捕获环境变量:
fn main() { // 1. 不可变借用 (通过 Fn) let x = 10; let print_x = || println!("x = {}", x); print_x(); println!("x 仍然可用: {}", x); // x 未被消耗
// 2. 可变借用 (通过 FnMut) let mut y = 10; { let mut increment_y = || { y += 1; }; increment_y(); increment_y(); } println!("y = {}", y); // y 已经被修改
// 3. 获取所有权 (通过 FnOnce) let z = vec![1, 2, 3]; let consume_z = || { let _owned = z; // z 被移动到闭包中 println!("z 被消耗"); }; consume_z(); // println!("{:?}", z); // 编译错误!z 已经被消耗
// 4. 强制使用 move let w = vec![1, 2, 3]; let use_w = move || { println!("w 通过 move 转移: {:?}", w); }; use_w(); // w 在闭包外部仍然可用,因为 i32 可以 Copy println!("w 仍然可用");}5. 闭包作为函数参数
Section titled “5. 闭包作为函数参数”闭包常作为函数参数,使用trait bound指定:
fn apply<F>(f: F)where F: Fn(),{ f();}
fn apply_with_value<F, T>(f: F, value: T)where F: Fn(T) -> T,{ f(value);}
fn main() { let x = 10; apply(|| println!("闭包被调用,x = {}", x));
let result = apply_with_value(|n| n * 2, 5); println!("result = {}", result);
// 使用 Fn trait bound fn call_with_one<F>(f: F) -> i32 where F: Fn(i32) -> i32, { f(1) }
let result = call_with_one(|x| x + 10); println!("call_with_one result: {}", result);}6. 闭包与迭代器
Section titled “6. 闭包与迭代器”闭包在迭代器中使用非常频繁:
fn main() { let nums = vec![1, 2, 3, 4, 5];
// map: 转换每个元素 let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect(); println!("doubled: {:?}", doubled);
// filter: 过滤元素 let evens: Vec<_> = nums.iter().filter(|x| *x % 2 == 0).collect(); println!("evens: {:?}", evens);
// chain: 链接多个迭代器 let more_nums = vec![6, 7, 8]; let combined: Vec<_> = nums.iter().chain(&more_nums).collect(); println!("combined: {:?}", combined);
// fold: 聚合操作 let sum = nums.iter().fold(0, |acc, x| acc + x); println!("sum: {}", sum);
// 组合使用 let result: Vec<_> = nums .iter() .filter(|x| *x % 2 == 0) .map(|x| x * x) .collect(); println!("偶数的平方: {:?}", result);}今天我们学习了Rust闭包的核心知识:
- 闭包语法:
|params| expr或|params| { block } - 类型推导:编译器自动推导闭包参数和返回类型
- 三个Trait:
FnOnce:消耗捕获变量,只能调用一次FnMut:可变借用捕获变量,可多次调用Fn:只读借用捕获变量,可多次调用
- 捕获方式:不可变借用、可变借用、移动所有权
- 与迭代器结合:
map、filter、fold等 adaptor 大量使用闭包
闭包是Rust函数式编程的重要工具,熟练掌握可以写出更优雅的代码。明天我们将学习迭代器,这是Rust另一个强大的函数式特性。