闭包(Closure)是 Rust 中一种可以捕获环境变量的匿名函数。与普通函数不同,闭包可以保存在变量中、作为参数传递给其他函数,并且能够捕获定义时所在作用域的变量。闭包是 Rust 函数式编程特性的核心组成部分。
1. 闭包基础
1.1 定义闭包
闭包使用 || 语法定义,参数放在竖线之间,函数体跟在后面:
1 2 3 4 5 6 7
| fn main() { let add_one = |x| x + 1;
println!("结果: {}", add_one(5)); }
|
1.2 闭包的语法形式
闭包有多种等价的写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| fn main() { let add_one_v1 = |x: u32| -> u32 { x + 1 };
let add_one_v2 = |x: u32| { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1;
println!("{}", add_one_v4(5)); }
|
注意:一旦闭包的类型被推断确定,就不能再用不同类型调用它。
1.3 闭包与函数的对比
1 2
| fn add_one_fn (x: u32) -> u32 { x + 1 } let add_one_cl = |x: u32| -> u32 { x + 1 };
|
主要区别:
- 闭包使用
|| 而不是 ()
- 闭包的类型标注通常是可选的(可以被推断)
- 最重要的区别:闭包可以捕获环境变量,函数不能
2. 捕获环境
2.1 不可变借用
闭包默认以最小权限捕获变量:
1 2 3 4 5 6 7 8 9 10 11 12
| fn main() { let list = vec![1, 2, 3]; println!("定义闭包前: {:?}", list);
let only_borrows = || println!("闭包中: {:?}", list);
println!("调用闭包前: {:?}", list); only_borrows(); println!("调用闭包后: {:?}", list); }
|
2.2 可变借用
如果闭包需要修改变量,会自动使用可变借用:
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let mut list = vec![1, 2, 3]; println!("调用前: {:?}", list);
let mut borrows_mutably = || list.push(7);
borrows_mutably(); println!("调用后: {:?}", list); }
|
2.3 获取所有权
在某些情况下,闭包会获取变量的所有权:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| use std::thread;
fn main() { let list = vec![1, 2, 3]; println!("主线程: {:?}", list);
thread::spawn(move || println!("线程中: {:?}", list)) .join() .unwrap();
}
|
注意:使用 move 关键字强制闭包获取所有权,这在多线程编程中很常见。
3. 闭包的三种 Trait
Rust 的闭包会自动实现以下一个或多个 trait,这决定了闭包如何使用捕获的变量:
3.1 Fn - 不可变借用
1 2 3 4 5 6 7 8 9 10 11 12 13
|
fn apply_fn<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32, { f(x) }
fn main() { let double = |x| x * 2; println!("结果: {}", apply_fn(double, 5)); }
|
特点:
- 闭包只是读取捕获的变量(不可变借用)
- 可以被多次调用
- 最灵活的闭包类型
3.2 FnMut - 可变借用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| fn apply_fn_mut<F>(mut f: F, x: i32) -> i32 where F: FnMut(i32) -> i32, { f(x) }
fn main() { let mut total = 0;
let mut accumulate = |x| { total += x; total };
println!("累加后: {}", accumulate(5)); println!("累加后: {}", accumulate(3)); }
|
特点:
- 闭包修改了捕获的变量(可变借用)
- 可以被多次调用
- 需要声明为
mut
3.3 FnOnce - 消耗捕获的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| fn consume_with_closure<F>(func: F) where F: FnOnce(), { func(); }
fn main() { let s = String::from("hello");
let consume = || drop(s);
consume_with_closure(consume); }
|
特点:
- 闭包获取并消耗了捕获变量的所有权
- 只能被调用一次
- 最受限的闭包类型
4. 实际应用示例
4.1 结合迭代器使用
闭包最常见的用法是和迭代器一起使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| fn main() { let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter() .map(|x| x * 2) .collect();
println!("翻倍后: {:?}", doubled);
let evens: Vec<&i32> = numbers.iter() .filter(|&x| x % 2 == 0) .collect();
println!("偶数: {:?}", evens); }
|
4.2 结合 sort_by_key 使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #[derive(Debug)] struct User { name: String, age: u32, }
fn main() { let mut users = vec![ User { name: String::from("Alice"), age: 30 }, User { name: String::from("Bob"), age: 25 }, User { name: String::from("Charlie"), age: 35 }, ];
users.sort_by_key(|u| u.age);
println!("按年龄排序: {:#?}", users); }
|
4.3 延迟计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| struct Cacher<T> where T: Fn(u32) -> u32, { calculation: T, value: Option<u32>, }
impl<T> Cacher<T> where T: Fn(u32) -> u32, { fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: None, } }
fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v } } } }
fn main() { let mut expensive = Cacher::new(|num| { println!("慢速计算中..."); std::thread::sleep(std::time::Duration::from_secs(2)); num });
println!("结果: {}", expensive.value(10)); println!("结果: {}", expensive.value(10)); }
|
5. 闭包与所有权
5.1 Copy 类型的捕获
1 2 3 4 5 6 7 8 9 10 11 12
| fn main() { let x = 5;
let equal_to_x = move |z| z == x;
println!("x 仍可用: {}", x);
let y = 5; assert!(equal_to_x(y)); }
|
5.2 非 Copy 类型的捕获
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let s = String::from("hello");
let print_s = || println!("{}", s);
print_s(); print_s();
println!("{}", s); }
|
5.3 move 导致的所有权转移
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let s = String::from("hello");
let consume = move || { let _s = s; };
consume(); }
|
6. 函数指针与闭包
函数指针可以作为不捕获环境的闭包使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| fn add_one(x: i32) -> i32 { x + 1 }
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) }
fn main() { let answer = do_twice(add_one, 5); println!("结果: {}", answer);
let answer2 = do_twice(|x| x + 1, 5); println!("结果: {}", answer2); }
|
7. 返回闭包
由于闭包的大小在编译时未知,返回闭包需要使用 trait 对象:
1 2 3 4 5 6 7 8 9 10
|
fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }
fn main() { let f = returns_closure(); println!("结果: {}", f(5)); }
|
或使用 impl Trait 语法(更简洁,但需要单一具体类型):
1 2 3 4 5 6 7 8 9 10
|
fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 }
fn main() { let f = returns_closure(); println!("结果: {}", f(5)); }
|
8. 常见应用场景
8.1 错误处理
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let numbers = vec!["1", "2", "three", "4"];
let parsed: Vec<i32> = numbers.iter() .filter_map(|s| s.parse().ok()) .collect();
println!("解析成功: {:?}", parsed); }
|
8.2 自定义迭代器适配器
1 2 3 4 5 6 7 8 9 10 11
| fn main() { let numbers = vec![1, 2, 3, 4, 5];
let sum: i32 = numbers.iter() .map(|x| x * x) .filter(|x| x % 2 == 0) .sum();
println!("偶数平方和: {}", sum); }
|
8.3 回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
fn process_data<F>(data: Vec<i32>, callback: F) where F: Fn(i32), { for item in data { callback(item); } }
fn main() { let data = vec![1, 2, 3, 4, 5];
process_data(data, |x| { println!("处理: {}", x); }); }
|
总结
闭包是 Rust 中强大而灵活的特性,它能够:
- 简洁表达:用简短的语法定义匿名函数
- 捕获环境:访问定义时作用域内的变量
- 类型推断:编译器自动推断参数和返回值类型
- 零成本抽象:编译后性能与手写代码相当
- 灵活的所有权:根据需要选择借用或移动语义
三种 Trait 总结:
Fn:只读取捕获的变量,可以多次调用
FnMut:可以修改捕获的变量,可以多次调用
FnOnce:会消耗捕获的变量,只能调用一次
掌握闭包是编写惯用的、高性能 Rust 代码的关键。它们与迭代器结合使用,能够写出既简洁又高效的代码,是 Rust 函数式编程风格的核心。
Hooray!Closures 小节完成!!!