Rust 闭包
捕获方式、Fn/FnMut/FnOnce、move 关键字。
Key Points闭包按需要自动选择借用、可变借用或移动;`move` 强制移动。
基本语法
Rust
fn main() {
let add = |a: i32, b: i32| a + b;
println!("{}", add(1, 2));
// 类型可以省略
let double = |x| x * 2;
println!("{}", double(5));
// 多行需要花括号
let calc = |x: i32| {
let y = x * 2;
y + 1
};
println!("{}", calc(3));
}Output
3
10
7捕获
Rust
fn main() {
let name = String::from("Rust");
// 不可变借用
let greet = || println!("你好 {}", name);
greet();
greet();
println!("{} 还能用", name);
// 可变借用
let mut count = 0;
let mut inc = || {
count += 1;
println!("{}", count);
};
inc();
inc();
}Output
你好 Rust
你好 Rust
Rust 还能用
1
2三种 Fn trait
| Trait | Captures | Can call |
|---|---|---|
Fn | &self(不可变借用) | 任意次 |
FnMut | &mut self(可变借用) | 多次,但需可变引用 |
FnOnce | self(移动) | 一次 |
Rust
fn call_fn<F: Fn()>(f: F) {
f();
f();
}
fn call_once<F: FnOnce()>(f: F) {
f();
}
fn main() {
call_fn(|| println!("Fn 可以多次调用"));
let s = String::from("只配移动");
call_once(move || println!("{}", s));
}Output
Fn 可以多次调用
Fn 可以多次调用
只配移动The trait follows the capture: read-only → Fn, mutate → FnMut, take ownership → FnOnce. They nest: an Fn closure also satisfies FnMut and FnOnce, not the reverse. So F: FnOnce() accepts every closure while F: Fn() accepts only non-mutating, non-moving ones.
move
Rust
use std::thread;
fn main() {
let data = vec![1, 2, 3];
// 不 move 会报错:闭包可能比 data 活得久
let handle = thread::spawn(move || {
println!("{:?}", data);
});
handle.join().unwrap();
}Output
[1, 2, 3]move captures all used variables by value; it does not move the closure. Threads require it because the compiler cannot prove a reference outlives the thread.闭包作为返回值
Rust
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let add5 = make_adder(5);
println!("{}", add5(10));
let add10 = make_adder(10);
println!("{}", add10(10));
}Output
15
20Returning a closure needs impl Fn(...) (or Box<dyn Fn(...)>) because the closure's type is anonymous and unique. move is required here: n is local and dies when the function returns.
实用例子
Rust
fn main() {
// 排序
let mut v = vec![("b", 2), ("a", 3), ("c", 1)];
v.sort_by(|x, y| x.1.cmp(&y.1));
println!("{:?}", v);
// 计数
let mut counter = 0;
let mut tick = || {
counter += 1;
counter
};
tick();
tick();
println!("{}", tick());
}Output
[("c", 1), ("b", 2), ("a", 3)]
3基本语法
Rust
fn main() {
let add = |a: i32, b: i32| a + b;
println!("{}", add(1, 2));
// 类型可以省略
let double = |x| x * 2;
println!("{}", double(5));
// 多行需要花括号
let calc = |x: i32| {
let y = x * 2;
y + 1
};
println!("{}", calc(3));
}Output
3
10
7捕获
Rust
fn main() {
let name = String::from("Rust");
// 不可变借用
let greet = || println!("你好 {}", name);
greet();
greet();
println!("{} 还能用", name);
// 可变借用
let mut count = 0;
let mut inc = || {
count += 1;
println!("{}", count);
};
inc();
inc();
}Output
你好 Rust
你好 Rust
Rust 还能用
1
2三种 Fn trait
| Trait | 捕获方式 | 可调用次数 |
|---|---|---|
Fn | &self(不可变借用) | 任意次 |
FnMut | &mut self(可变借用) | 多次,但需可变引用 |
FnOnce | self(移动) | 一次 |
Rust
fn call_fn<F: Fn()>(f: F) {
f();
f();
}
fn call_once<F: FnOnce()>(f: F) {
f();
}
fn main() {
call_fn(|| println!("Fn 可以多次调用"));
let s = String::from("只配移动");
call_once(move || println!("{}", s));
}Output
Fn 可以多次调用
Fn 可以多次调用
只配移动闭包实现哪个 trait 由捕获方式决定:只读捕获 → Fn,修改捕获 → FnMut,拿走所有权 → FnOnce。三者是包含关系:Fn 也能当 FnMut 和 FnOnce 用,反之不行。所以参数写 F: FnOnce() 能接受所有闭包,写 F: Fn() 只接受不修改、不移动的。
move
Rust
use std::thread;
fn main() {
let data = vec![1, 2, 3];
// 不 move 会报错:闭包可能比 data 活得久
let handle = thread::spawn(move || {
println!("{:?}", data);
});
handle.join().unwrap();
}Output
[1, 2, 3]move 是按值捕获所有用到的变量,不是「移动闭包本身」。跨线程必须用 move,因为编译器无法保证引用的生命周期覆盖线程。闭包作为返回值
Rust
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |x| x + n
}
fn main() {
let add5 = make_adder(5);
println!("{}", add5(10));
let add10 = make_adder(10);
println!("{}", add10(10));
}Output
15
20返回闭包必须用 impl Fn(...)(或 Box<dyn Fn(...)>),因为闭包的具体类型是匿名且唯一的,写不出来。move 在这里必需——n 是局部变量,函数返回后就不存在了。
实用例子
Rust
fn main() {
// 排序
let mut v = vec![("b", 2), ("a", 3), ("c", 1)];
v.sort_by(|x, y| x.1.cmp(&y.1));
println!("{:?}", v);
// 计数
let mut counter = 0;
let mut tick = || {
counter += 1;
counter
};
tick();
tick();
println!("{}", tick());
}Output
[("c", 1), ("b", 2), ("a", 3)]
3