闭包
你会学到什么
- 闭包是能捕获周围变量的匿名函数。
- 按捕获方式分为
Fn(只读)、FnMut(可变)、FnOnce(消费)。 move把捕获的变量移动进闭包,常用于返回闭包或跨线程。
最小示例
let make_adder = |n: i32| move |x: i32| x + n;
let add_ten = make_adder(10);
assert_eq!(add_ten(3), 13);
运行代码
cd examples
cargo run -p rt_19_closures
cargo test -p rt_19_closures
代码讲解
把闭包当参数,用泛型 + trait bound 接收:
fn apply<F: Fn(i32) -> i32>(function: F, value: i32) -> i32 {
function(value)
}
返回闭包用 impl Fn,并用 move 把捕获值的所有权交给闭包:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |value| value + n
}
需要修改捕获状态时,闭包是 FnMut:
fn make_counter() -> impl FnMut() -> i32 {
let mut count = 0;
move || { count += 1; count }
}
常见错误
返回借用了局部变量的闭包却没加 move:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
|value| value + n // ❌ n 在函数结束后失效
}
加上 move 让闭包获得 n 的所有权。
练习
- 写一个函数接受
FnMut闭包并调用它三次。 - 用闭包 +
make_adder组合出一个“先加 1 再乘 2”的函数。
小结
闭包 = 匿名函数 + 环境捕获。Fn/FnMut/FnOnce 描述捕获强度,move 转移所有权。
下一步
闭包最常和迭代器搭配。下一章进入迭代器与函数式风格。
完整示例代码
下面是 examples/19_closures/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/19_closures/src/main.rs
//! 闭包与 Fn / FnMut / FnOnce。
/// 接受一个 Fn 闭包并调用它。
fn apply<F: Fn(i32) -> i32>(function: F, value: i32) -> i32 {
function(value)
}
/// 返回一个捕获了 n 的闭包(impl Fn)。
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
move |value| value + n
}
/// 返回一个会修改内部状态的 FnMut 闭包。
fn make_counter() -> impl FnMut() -> i32 {
let mut count = 0;
move || {
count += 1;
count
}
}
fn main() {
println!("apply double 5 = {}", apply(|x| x * 2, 5));
let add_ten = make_adder(10);
println!("add_ten(3) = {}", add_ten(3));
let mut counter = make_counter();
println!("counter: {} {} {}", counter(), counter(), counter());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn applies_closure() {
assert_eq!(apply(|x| x + 1, 41), 42);
}
#[test]
fn adder_captures_value() {
let add_five = make_adder(5);
assert_eq!(add_five(10), 15);
}
#[test]
fn counter_keeps_state() {
let mut counter = make_counter();
assert_eq!(counter(), 1);
assert_eq!(counter(), 2);
assert_eq!(counter(), 3);
}
} examples/19_closures/Cargo.toml
[package]
name = "rt_19_closures"
version.workspace = true
edition.workspace = true
publish.workspace = true