Skip to content

Day 16: 闭包

闭包是匿名函数,可以捕获其定义环境中的变量。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));
}

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);
}

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(); // 可以多次调用
}

闭包以三种方式捕获环境变量:

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 仍然可用");
}

闭包常作为函数参数,使用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);
}

闭包在迭代器中使用非常频繁:

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另一个强大的函数式特性。