24·函数式编程进阶

闭包

闭包

学习目标

  1. 理解闭包语法
  2. 掌握三种捕获模式
  3. 理解 FnFnMutFnOnce 的区别
  4. 掌握闭包作为参数和返回值

核心概念

闭包语法

fn main() {
    // 基本闭包
    let add = |a, b| a + b;
    println!("{}", add(2, 3));

    // 带类型标注
    let add = |a: i32, b: i32| -> i32 { a + b };

    // 多行闭包
    let complex = |x: i32| {
        let doubled = x * 2;
        let result = doubled + 1;
        result
    };

    // 闭包可以捕获环境变量
    let offset = 10;
    let add_offset = |x| x + offset;
    println!("{}", add_offset(5));  // 15
}

捕获模式

fn main() {
    // 不可变借用(&T)
    let name = String::from("Rust");
    let greet = || println!("Hello, {}!", name);
    greet();
    greet();  // 可以多次调用
    println!("name 仍可用: {}", name);

    // 可变借用(&mut T)
    let mut count = 0;
    let mut increment = || { count += 1; };
    increment();
    increment();
    println!("count = {}", count);

    // 移动所有权(T)
    let name = String::from("Rust");
    let consume = move || {
        println!("consumed: {}", name);
    };
    consume();
    // println!("{}", name);  // ❌ 已移动
}

三种 Fn Trait

// FnOnce: 只能调用一次(消耗捕获的值)
fn call_once<F: FnOnce() -> String>(f: F) -> String {
    f()  // 只调用一次
}

// FnMut: 可以修改捕获的值
fn call_mut<F: FnMut()>(mut f: F) {
    f();
    f();
}

// Fn: 不修改,不消耗
fn call_fn<F: Fn()>(f: F) {
    f();
    f();
}

fn main() {
    let name = String::from("Rust");

    // FnOnce
    let consume = move || name;
    call_once(consume);

    // FnMut
    let mut count = 0;
    let mut inc = || { count += 1; };
    call_mut(&mut inc);
    println!("count = {}", count);

    // Fn
    let greet = || println!("hello");
    call_fn(greet);
}

闭包作为参数

fn apply<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

fn main() {
    let double = |x| x * 2;
    let add_one = |x| x + 1;

    println!("{}", apply(double, 5));    // 10
    println!("{}", apply(add_one, 5));   // 6
}

闭包作为返回值

fn make_adder(offset: i32) -> impl Fn(i32) -> i32 {
    move |x| x + offset  // 必须用 move 转移所有权
}

fn make_multiplier(factor: f64) -> Box<dyn Fn(f64) -> f64> {
    Box::new(move |x| x * factor)
}

fn main() {
    let add_5 = make_adder(5);
    println!("{}", add_5(10));  // 15

    let double = make_multiplier(2.0);
    println!("{}", double(3.0));  // 6.0
}

闭包与迭代器

fn main() {
    let numbers = vec![1, 2, 3, 4, 5];

    // map
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();

    // filter
    let evens: Vec<&i32> = numbers.iter().filter(|&&x| x % 2 == 0).collect();

    // filter_map
    let parsed: Vec<i32> = vec!["1", "abc", "3"]
        .iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();

    // for_each
    numbers.iter().for_each(|x| print!("{} ", x));
}

实践练习

练习 1:函数组合

fn compose<F, G>(f: F, g: G) -> impl Fn(i32) -> i32
where
    F: Fn(i32) -> i32,
    G: Fn(i32) -> i32,
{
    move |x| f(g(x))
}

fn main() {
    let double = |x| x * 2;
    let add_one = |x| x + 1;
    let double_then_add = compose(add_one, double);
    println!("{}", double_then_add(5));  // 11
}

练习 2:缓存

struct Cache<F>
where
    F: Fn(i32) -> i32,
{
    calculation: F,
    value: Option<i32>,
}

impl<F> Cache<F>
where
    F: Fn(i32) -> i32,
{
    fn new(calculation: F) -> Self {
        Cache { calculation, value: None }
    }

    fn get(&mut self, input: i32) -> i32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(input);
                self.value = Some(v);
                v
            }
        }
    }
}

fn main() {
    let mut cache = Cache::new(|x| {
        println!("计算中...");
        x * x
    });
    println!("结果: {}", cache.get(5));
    println!("结果: {}", cache.get(5));  // 不会再次计算
}

常见错误

1. 忘记 move

fn make_adder(offset: i32) -> impl Fn(i32) -> i32 {
    // ❌ 编译错误:捕获了局部变量的引用
    // |x| x + offset

    // ✅
    move |x| x + offset
}

2. Fn vs FnMut 混淆

let mut count = 0;
let mut inc = || count += 1;
// fn call_fn<F: Fn()>(f: F) { f(); }
// call_fn(inc);  // ❌ inc 是 FnMut,不是 Fn

小结

Trait调用次数捕获方式
Fn多次不可变借用
FnMut多次可变借用
FnOnce一次获取所有权
语法说明
|x| x + 1闭包
move || { }强制获取所有权
impl Fn(T) -> U闭包参数/返回值

练习编辑器

rust
Loading...