Rust: Rust 闭包(Closures):匿名函数捕获环境

最后更新:2026-08-26

闭包(closure)是可以捕获周围环境变量的匿名函数。它像函数一样被调用,但能"记住"定义时所在作用域的变量。

如果说普通函数是"外卖菜单上的固定套餐"(食材从参数来),那闭包就是"你自己冰箱里的菜"(食材从环境来)。闭包可以访问定义时周围的变量——就像从冰箱里拿食材直接下厨。


1. 你将学到


2. 概念图解

以下 Mermaid 图展示闭包三种捕获方式(Fn / FnMut / FnOnce)的关系与区别:

100%
graph TB
    A["闭包 Closure"] --> B["Fn<br/>不可变借用 &T"]
    A --> C["FnMut<br/>可变借用 &mut T"]
    A --> D["FnOnce<br/>消费所有权 T"]

    B --> E["let c = || println!(&#34;{ }&#34;, x);"]
    B --> F["可多次调用"]
    B --> G["环境变量只读不可变"]

    C --> H["let mut c = || x += 1;"]
    C --> I["可多次调用"]
    C --> J["可修改环境变量"]

    D --> K["let c = || drop(x);"]
    D --> L["只能调用一次"]
    D --> M["所有权已移入闭包"]

    B -.-> |自动推断| N["编译器根据闭包体<br/>选择最低要求的 trait"]
    C -.-> N
    D -.-> N
    N --> O["move 关键字强制 FnOnce"]

3. 外卖评分系统的故事

(1) 痛苦:为每种评分规则写单独的函数

Mia (Mia) 在开发一个外卖评分系统。用户可以对店铺评分,但评分规则各不相同:

最开始她为每种规则写一个函数:

RUST
struct Review {
    store: String,
    rating: u32,
}

fn normal_score(r: &Review) -> u32 {
    r.rating
}

fn blogger_score(r: &Review) -> u32 {
    r.rating * 2
}

fn inspector_score(r: &Review) -> u32 {
    if r.rating < 3 { 0 } else { r.rating }
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    for r in &reviews {
        println!("{}: normal={}, blogger={}, inspector={}",
            r.store, normal_score(r), blogger_score(r), inspector_score(r));
    }
}

三个函数除了核心逻辑不同,函数签名完全一样。如果产品经理说"再加一个超级会员评分规则"又要多写一个函数。而且这些评分规则不能"动态生成"——比如"评分 × 系数"中的系数必须是写死在函数里的。

(2) 更复杂的需求:评分系数动态变化

产品经理说:评分系数每天变化(根据促销活动),而且用户可以选择不同的评分策略(打分、加权、惩罚低分)。用普通函数做不到——函数签名在编译时固定,无法捕获外部的动态系数。

RUST
// 想用函数,但系数是动态的
let weight = 1.5;  // 今天的权重系数
// fn weighted_score(r: &Review) -> u32 {
//     (r.rating as f64 * weight) as u32  // ❌ 编译错误:weight 不在函数作用域内
// }

(3) Rust 闭包的方案

RUST
struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: "Pizzaplace".into(), rating: 4 },
        Review { store: "BurgerKing".into(), rating: 2 },
        Review { store: "SushiBar".into(), rating: 5 },
    ];

    // Closure 1: normal score (no capture)
    let normal = |r: &Review| r.rating;

    // Closure 2: weighted score (captures `weight` from environment)
    let weight: f64 = 1.5;
    let weighted = |r: &Review| (r.rating as f64 * weight) as u32;

    // Closure 3: inspector (captures `threshold` from environment)
    let threshold = 3;
    let inspector = |r: &Review| if r.rating < threshold { 0 } else { r.rating };

    for r in &reviews {
        println!("{}: normal={}, weighted={}, inspector={}",
            r.store, normal(r), weighted(r), inspector(r));
    }
}

输出:

TEXT 📖 仅展示
Pizzaplace: normal=4, weighted=6, inspector=4
BurgerKing: normal=2, weighted=3, inspector=0
SushiBar: normal=5, weighted=7, inspector=5

闭包 |r| r.rating * weight 捕获了外部变量 weight——这是普通函数做不到的。weightthreshold 可以动态变化(比如来自用户输入或配置文件),闭包自动捕获当前值。这就是闭包的核心价值:一个可以从周围环境"偷"变量来用的函数


4. 核心概念

(1) 闭包体系总览

100%
graph TB
    A[Rust Closures] --> B[Syntax]
    A --> C[Capture Modes]
    A --> D[move Keyword]
    A --> E[As Parameters]
    A --> F[With Iterators]

    B --> B1["|param1, param2| expr"]
    B --> B2["|param| { multiple; statements; }"]

    C --> C1["Fn: immutable borrow (&T)"]
    C --> C2["FnMut: mutable borrow (&mut T)"]
    C --> C3["FnOnce: ownership (T)"]

    D --> D1["let c = move || x;"]
    D --> D2["Forces ownership transfer"]

    E --> E1["fn pointer: fn(T) -> U"]
    E --> E2["Fn trait: impl Fn(T) -> U"]
    E --> E3["FnMut / FnOnce trait bounds"]

    F --> F1["iter().map(|x| x + 1)"]
    F --> F2["iter().filter(|x| x > 0)"]
    F --> F3["Chained: map().filter().collect()"]

(2) 三种捕获模式对比

Trait 捕获方式 调用次数 是否可修改环境 是否可以多次调用
FnOnce 消费所有权(move) 一次 可消费环境变量 否(所有权已转移)
FnMut 可变借用 多次 可以修改环境变量
Fn 不可变借用 多次 不能修改环境变量

(3) 函数指针 fn vs 闭包 Fn

特性 函数指针 fn 闭包 Fn trait
是否捕获环境 否——只能使用参数 是——可以捕获环境变量
能否作为参数 能(更通用)
能否接受闭包 不能
类型大小 fn(T) -> U(指针大小) 不同闭包有不同大小
性能 确定 编译器可内联
语法 fn foo(x: i32) -> i32 |x| x + 1

(4) 闭包捕获方式对比

捕获方式 语法示例 所有权影响 闭包可调用次数 适用场景
不可变借用 |x| x + var 借用 &T 多次 只读访问环境变量
可变借用 |x| { var += 1; ... } 借用 &mut T 多次(独占) 需修改环境变量
移动所有权 move |x| x + var 获取 T 仅一次(若消耗) 闭包需比引用活得更久
无捕获 |x| x + 1 多次 等价于函数指针

5. 闭包示例

▶ 示例 1:闭包基础语法——外卖评分(难度 ⭐)

RUST
// ============================================
// Closure basics: syntax, type inference, calling
// ============================================

struct Review {
    store: String,
    rating: u32,
}

fn main() {
    let reviews = vec![
        Review { store: String::from("Pizzaplace"), rating: 4 },
        Review { store: String::from("BurgerKing"), rating: 2 },
        Review { store: String::from("SushiBar"), rating: 5 },
        Review { store: String::from("NoodleHouse"), rating: 3 },
    ];

    // --- Syntax variation 1: single expression ---
    let double = |r: &Review| r.rating * 2;
    // --- Syntax variation 2: block body ---
    let triple = |r: &Review| {
        let result = r.rating * 3;
        println!("    (triple: {} -> {})", r.rating, result);
        result
    };
    // --- Syntax variation 3: type inference ---
    // Rust infers parameter and return types from usage
    let half = |r| r.rating / 2;  // type inferred as &Review -> u32

    println!("--- Rating Calculations ---");
    for r in &reviews {
        println!("{} (rating: {}):", r.store, r.rating);
        println!("  double={}, half={}", double(r), half(r));
        let _ = triple(r);
    }

    // --- Closures that capture variables ---
    let min_rating = 3;
    let min_rating2 = 4;

    // Capture `min_rating` from the surrounding scope
    let is_pass = |r: &Review| r.rating >= min_rating;
    let is_good = |r: &Review| r.rating >= min_rating2;

    println!("\n--- Pass Check (min: {}) ---", min_rating);
    for r in &reviews {
        println!("{}: {}", r.store, if is_pass(r) { "PASS" } else { "FAIL" });
    }

    println!("\n--- Good Check (min: {}) ---", min_rating2);
    for r in &reviews {
        println!("{}: {}", r.store, if is_good(r) { "GOOD" } else { "OK" });
    }

    // --- Using closures as function arguments ---
    fn check_reviews(reviews: &[Review], label: &str, scorer: impl Fn(&Review) -> u32) {
        print!("{}: ", label);
        for r in reviews {
            print!("{}->{} ", r.store, scorer(r));
        }
        println!();
    }

    let weight = 2;
    let weighted_scorer = |r: &Review| r.rating * weight;

    check_reviews(&reviews, "Weighted(×2)", weighted_scorer);
    check_reviews(&reviews, "Normal", |r| r.rating);
    check_reviews(&reviews, "Bonus", |r| if r.rating >= 4 { r.rating + 1 } else { r.rating });
}

输出:

TEXT 📖 仅展示
--- Rating Calculations ---
Pizzaplace (rating: 4):
  double=8, half=2
    (triple: 4 -> 12)
BurgerKing (rating: 2):
  double=4, half=1
    (triple: 2 -> 6)
SushiBar (rating: 5):
  double=10, half=2
    (triple: 5 -> 15)
NoodleHouse (rating: 3):
  double=6, half=1
    (triple: 3 -> 9)

--- Pass Check (min: 3) ---
Pizzaplace: PASS
BurgerKing: FAIL
SushiBar: PASS
NoodleHouse: PASS

--- Good Check (min: 4) ---
Pizzaplace: GOOD
BurgerKing: OK
SushiBar: GOOD
NoodleHouse: OK

--- Weighted(×2) ---
Pizzaplace->8 BurgerKing->4 SushiBar->10 NoodleHouse->6
Normal: Pizzaplace->4 BurgerKing->2 SushiBar->5 NoodleHouse->3
Bonus: Pizzaplace->5 BurgerKing->2 SushiBar->6 NoodleHouse->3

闭包语法有三种风格:单表达式 |x| expr、块体 |x| { stmt; expr }、类型推断 |x| x+1(类型从上下文推断)。捕获环境变量是闭包的独特能力:is_pass 捕获了 min_ratingweighted_scorer 捕获了 weightcheck_reviews 函数接受 impl Fn(&Review) -> u32——任何实现了 Fn trait 的闭包都可以传入。


▶ 示例 2:捕获模式——Fn / FnMut / FnOnce(难度 ⭐⭐)

RUST
// ============================================
// Three capture modes: Fn, FnMut, FnOnce
// ============================================

fn main() {
    println!("=== Fn: Immutable Borrow ===");
    let scores = vec![10, 20, 30];
    // Fn closure: only reads captured variables (immutable borrow)
    let print_scores = || {
        println!("Scores: {:?}", scores);  // &Vec<i32>
    };
    print_scores();  // Can be called multiple times
    print_scores();
    println!("Still accessible: {:?}", scores);  // scores not moved

    println!("\n=== FnMut: Mutable Borrow ===");
    let mut counters = vec![0, 0, 0];
    // FnMut closure: can mutate captured variables
    let mut increment = || {
        for c in &mut counters {
            *c += 1;
        }
    };
    increment();  // Can be called multiple times
    increment();
    increment();
    println!("Counters: {:?}", counters);  // [3, 3, 3]

    println!("\n=== FnOnce: Ownership Consumed ===");
    let name = String::from("Pizzaplace");
    // FnOnce closure: consumes the captured variable
    let consume = || {
        println!("Consuming: {}", name);
        drop(name);  // Explicitly drop (consumes ownership)
    };
    consume();
    // consume();  // ❌ Compile error: closure can only be called once
    // println!("{}", name);  // ❌ Compile error: name was moved

    println!("\n=== FnOnce with move ===");
    let data = vec![1, 2, 3, 4, 5];
    // `move` forces the closure to take ownership of `data`
    let compute = move || {
        let sum: i32 = data.iter().sum();
        println!("Sum of data: {}", sum);
        // data is dropped here at end of closure
    };
    compute();
    // println!("{:?}", data);  // ❌ Compile error: data was moved into the closure
    // compute();  // ❌ Could also fail if the closure consumed data

    println!("\n=== Practical: Closure Type Selection ===");
    let items = vec!["apple", "banana", "cherry"];

    // Fn: read-only
    let count = || items.len();
    println!("Count (Fn): {}", count());

    // FnMut: modify captured variable
    let mut store = String::new();
    let mut append = |item: &str| {
        if !store.is_empty() { store.push_str(", "); }
        store.push_str(item);
    };
    for item in &items {
        append(item);
    }
    println!("Store (FnMut modified): {}", store);
    let store_len = store.len();
    println!("Store length: {}", store_len);
}

输出:

TEXT 📖 仅展示
=== Fn: Immutable Borrow ===
Scores: [10, 20, 30]
Scores: [10, 20, 30]
Still accessible: [10, 20, 30]

=== FnMut: Mutable Borrow ===
Counters: [3, 3, 3]

=== FnOnce: Ownership Consumed ===
Consuming: Pizzaplace

=== FnOnce with move ===
Sum of data: 15

=== Practical: Closure Type Selection ===
Count (Fn): 3
Store (FnMut modified): apple, banana, cherry
Store length: 20

编译器根据闭包体对捕获变量的操作方式自动推断使用哪个 trait:只读访问Fn可变访问FnMut(必须用 mut 声明闭包),消费所有权FnOnce(只能调用一次)。move 关键字强制将所有权移入闭包——常用于多线程场景(thread::spawn 需要 'static 生命周期)。


▶ 示例 3:闭包作为参数——函数指针 vs Fn trait(难度 ⭐⭐)

RUST
// ============================================
// Closures as parameters: fn pointer vs Fn trait
// ============================================

// --- Version 1: Accepts ONLY function pointers ---
// fn(T) -> U is a function pointer type (no environment capture)
fn apply_fn_pointer(f: fn(i32) -> i32, x: i32) -> i32 {
    f(x)
}

// --- Version 2: Accepts ANY Fn trait (including closures) ---
// impl Fn(T) -> U accepts both fn pointers and closures
fn apply_fn<F>(f: F, x: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(x)
}

// --- Version 3: FnMut parameter ---
fn apply_twice<F>(mut f: F, x: i32) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(f(x))
}

// --- Version 4: FnOnce parameter ---
fn apply_once<F>(f: F, x: i32) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(x)
}

// A regular function (can be used as fn pointer)
fn square(x: i32) -> i32 {
    x * x
}

fn main() {
    // --- Regular functions as fn pointers ---
    println!("--- Function Pointers ---");
    // `square` is a function, can be passed as fn(i32) -> i32
    println!("square(5) via fn ptr: {}", apply_fn_pointer(square, 5));

    // Annotated closure (no capture) can be coerced to fn pointer
    let triple = |x: i32| x * 3;
    println!("triple(5) via fn ptr: {}", apply_fn_pointer(triple, 5));

    // --- Closures with Fn trait ---
    println!("\n--- Fn Trait (generic) ---");
    let base = 10;
    // This closure captures `base`, so it CANNOT be a fn pointer
    let add_base = |x: i32| x + base;
    println!("add_base(5): {}", apply_fn(add_base, 5));

    // Both fn pointers and closures work with Fn trait
    println!("square(6) via Fn: {}", apply_fn(square, 6));

    // --- FnMut in action ---
    println!("\n--- FnMut ---");
    let mut accum = 0;
    let mut accumulate = |x: i32| {
        accum += x;
        accum
    };
    println!("accumulate(5): {}", apply_twice(&mut accumulate, 5));
    // After apply_twice: accum = 5 + 5 = 10, then 10 + 5 = 15
    println!("Final accum: {}", accum);

    // --- FnOnce in action ---
    println!("\n--- FnOnce ---");
    let owned = String::from("value: ");
    let describe = |x: i32| {
        println!("{} {}", owned, x);
        x
    };
    println!("describe(42): {}", apply_once(describe, 42));
    // describe was consumed (FnOnce), cannot call again

    // --- Practical: scoring system with different strategies ---
    println!("\n--- Practical: Scoring Strategies ---");

    fn run_strategy<F>(reviews: &[u32], label: &str, strategy: F)
    where
        F: Fn(u32) -> u32,
    {
        let scores: Vec<u32> = reviews.iter().map(|&r| strategy(r)).collect();
        println!("{}: {:?}", label, scores);
    }

    let ratings = [4, 2, 5, 3, 1];
    let bonus_threshold = 4;
    let bonus_points = 1;

    // Different scoring strategies, all as closures
    run_strategy(&ratings, "Normal", |r| r);
    run_strategy(&ratings, "Double", |r| r * 2);
    run_strategy(&ratings, "Bonus", |r| if r >= bonus_threshold { r + bonus_points } else { r });
    run_strategy(&ratings, "Penalty", |r| if r < 3 { 0 } else { r });
}

输出:

TEXT 📖 仅展示
--- Function Pointers ---
square(5) via fn ptr: 25
triple(5) via fn ptr: 15

--- Fn Trait (generic) ---
add_base(5): 15
square(6) via Fn: 36

--- FnMut ---
accumulate(5): 15
Final accum: 15

--- FnOnce ---
value:  42
describe(42): 42

--- Practical: Scoring Strategies ---
Normal: [4, 2, 5, 3, 1]
Double: [8, 4, 10, 6, 2]
Bonus: [5, 2, 6, 3, 1]
Penalty: [4, 0, 5, 3, 0]

函数指针 fn(i32) -> i32 只能接受普通函数和不捕获环境的闭包。Fn trait(泛型)可以接受任何闭包(包括捕获环境的)。选择规则:接受 fn 指针用于 FFI 或需要存储固定的函数类型;接受 Fn/FnMut/FnOnce trait 用于需要闭包捕获环境的场景。run_strategy 函数接受 impl Fn(u32) -> u32——四种不同的评分策略作为闭包传入,包括捕获了 bonus_thresholdbonus_points 的 Bonus 策略。


▶ 示例 4:闭包与迭代器组合——map / filter / collect(难度 ⭐⭐⭐)

RUST
// ============================================
// Closures + Iterator combinators: map, filter, collect
// ============================================

#[derive(Debug)]
struct Order {
    store: String,
    items: Vec<String>,
    rating: u32,
}

fn main() {
    let orders = vec![
        Order {
            store: String::from("Pizzaplace"),
            items: vec!["Margherita".into(), "Cola".into()],
            rating: 4,
        },
        Order {
            store: String::from("BurgerKing"),
            items: vec!["Whopper".into(), "Fries".into(), "Shake".into()],
            rating: 2,
        },
        Order {
            store: String::from("SushiBar"),
            items: vec!["Salmon".into(), "Tuna".into()],
            rating: 5,
        },
        Order {
            store: String::from("NoodleHouse"),
            items: vec!["Ramen".into(), "Gyoza".into(), "Tea".into()],
            rating: 3,
        },
    ];

    // --- map: transform each element ---
    println!("--- map: store names uppercase ---");
    let store_names: Vec<String> = orders
        .iter()
        .map(|o| o.store.to_uppercase())
        .collect();
    println!("{:?}", store_names);

    // --- filter: keep elements matching a condition ---
    println!("\n--- filter: orders with rating >= 4 ---");
    let good_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .collect();
    for o in &good_orders {
        println!("{:?}", o);
    }

    // --- filter + map: chained ---
    println!("\n--- filter + map: good store names ---");
    let good_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= 4)
        .map(|o| format!("★ {} (rating: {})", o.store, o.rating))
        .collect();
    for s in &good_stores {
        println!("{}", s);
    }

    // --- map + filter: compute and refine ---
    println!("\n--- map + filter: average rating per item ---");
    let avg_ratings: Vec<String> = orders
        .iter()
        .map(|o| {
            // Compute average rating per item
            let item_count = o.items.len() as f64;
            let avg = o.rating as f64 / item_count;
            (o.store.clone(), avg, o.items.len())
        })
        .filter(|(_, avg, _)| *avg > 1.5)  // Only stores with high per-item rating
        .map(|(store, avg, count)| format!("{}: {:.2}/item ({} items)", store, avg, count))
        .collect();
    for s in &avg_ratings {
        println!("{}", s);
    }

    // --- Advanced: closures capturing external state ---
    println!("\n--- Advanced: dynamic threshold filtering ---");
    let min_rating = 3;
    let category = "Premium";

    let premium_stores: Vec<String> = orders
        .iter()
        .filter(|o| o.rating >= min_rating)
        .map(|o| format!("[{}] {} (rating: {})", category, o.store, o.rating))
        .collect();

    for s in &premium_stores {
        println!("{}", s);
    }

    // --- All items from high-rated stores ---
    println!("\n--- Flat items from high-rated stores ---");
    let threshold = 3;
    let recommended_items: Vec<&String> = orders
        .iter()
        .filter(|o| o.rating > threshold)
        .flat_map(|o| o.items.iter())
        .collect();
    println!("Recommended: {:?}", recommended_items);

    // --- Custom scoring with filter ---
    println!("\n--- Custom filter: weighted score ---");
    let weight = 1.2;
    let min_score = 4.0;

    let top_orders: Vec<&Order> = orders
        .iter()
        .filter(|o| (o.rating as f64 * weight) >= min_score)
        .collect();
    for o in &top_orders {
        let weighted = o.rating as f64 * weight;
        println!("{}: raw={}, weighted={:.1}, pass={}", o.store, o.rating, weighted, weighted >= min_score);
    }

    // --- count, any, all ---
    println!("\n--- Aggregate checks ---");
    let high_rated_count = orders.iter().filter(|o| o.rating >= 4).count();
    println!("Orders with rating >= 4: {}", high_rated_count);

    let has_bad_order = orders.iter().any(|o| o.rating <= 1);
    println!("Has any bad order (rating <= 1): {}", has_bad_order);

    let all_rated = orders.iter().all(|o| o.rating >= 1);
    println!("All orders rated at least 1: {}", all_rated);
}

输出:

TEXT 📖 仅展示
--- map: store names uppercase ---
["PIZZAPLACE", "BURGERKING", "SUSHIBAR", "NOODLEHOUSE"]

--- filter: orders with rating >= 4 ---
Order { store: "Pizzaplace", items: ["Margherita", "Cola"], rating: 4 }
Order { store: "SushiBar", items: ["Salmon", "Tuna"], rating: 5 }

--- filter + map: good store names ---
★ Pizzaplace (rating: 4)
★ SushiBar (rating: 5)

--- map + filter: average rating per item ---
Pizzaplace: 2.00/item (2 items)
SushiBar: 2.50/item (2 items)

--- Advanced: dynamic threshold filtering ---
[Premium] Pizzaplace (rating: 4)
[Premium] SushiBar (rating: 5)
[Premium] NoodleHouse (rating: 3)

--- Flat items from high-rated stores ---
Recommended: ["Margherita", "Cola", "Salmon", "Tuna"]

--- Custom filter: weighted score ---
Pizzaplace: raw=4, weighted=4.8, pass=true
SushiBar: raw=5, weighted=6.0, pass=true

--- Aggregate checks ---
Orders with rating >= 4: 2
Has any bad order (rating <= 1): false
All orders rated at least 1: true

迭代器组合子是闭包最强大的应用场景之一:map(转换每个元素)、filter(按条件保留元素)、flat_map(展平嵌套迭代器)、any/all(聚合检查)、count(计数)。闭包在这里的价值在于:可以捕获外部动态值(如 min_ratingweightcategory),让数据处理的逻辑与配置解耦。链式调用 iter().filter().map().collect() 是 Rust 中最具表现力的惯用法。


▶ 示例 5:综合练习——闭包实现配置驱动的数据管道(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:闭包捕获 + 高阶函数 + 迭代器链
// ============================================

struct DataPipeline<'a, T> {
    data: Vec<T>,
    filters: Vec<Box<dyn Fn(&T) -> bool + 'a>>,
    transforms: Vec<Box<dyn Fn(T) -> T + 'a>>,
}

impl<'a, T: Clone + 'a> DataPipeline<'a, T> {
    fn new(data: Vec<T>) -> Self {
        DataPipeline { data, filters: Vec::new(), transforms: Vec::new() }
    }

    fn filter<F: Fn(&T) -> bool + 'a>(mut self, f: F) -> Self {
        self.filters.push(Box::new(f));
        self
    }

    fn map<F: Fn(T) -> T + 'a>(mut self, f: F) -> Self {
        self.transforms.push(Box::new(f));
        self
    }

    fn execute(self) -> Vec<T> {
        let mut result = self.data;
        for f in &self.filters {
            result.retain(f);
        }
        for t in &self.transforms {
            result = result.into_iter().map(t).collect();
        }
        result
    }
}

fn main() {
    let min_score = 60;
    let bonus = 10;
    let max_score = 100;

    let pipeline = DataPipeline::new(vec![85, 42, 95, 58, 73, 30, 88])
        .filter(move |&&x| x >= min_score)
        .filter(|&&x| x < max_score)
        .map(move |x| x + bonus);

    let result = pipeline.execute();
    println!("筛选+加分后: {:?}", result);

    let multiplier = 2;
    let adjusted: Vec<i32> = result.into_iter()
        .map(|x| x * multiplier)
        .filter(|x| *x < 200)
        .collect();
    println!("翻倍+上限过滤: {:?}", adjusted);

    let threshold = 150;
    let has_high = adjusted.iter().any(|&x| x > threshold);
    let all_above_100 = adjusted.iter().all(|&x| x > 100);
    println!("有超过{}的: {}, 全部>100: {}", threshold, has_high, all_above_100);
}

输出:

TEXT 📖 仅展示
筛选+加分后: [95, 83, 98]
翻倍+上限过滤: [190, 166, 196]
有超过150的: true, 全部>100: true

DataPipelineBox<dyn Fn> 存储闭包,move 捕获外部配置值(min_scorebonus),链式调用 filter().map() 构建处理管道。闭包让"配置与逻辑分离"成为可能。


❓ 常见问题

Q 闭包和普通函数的根本区别是什么?
A 闭包可以捕获周围作用域的变量,普通函数不能。
Q FnFnMutFnOnce 编译器是怎么选的?
A 编译器根据闭包体中对捕获变量的操作自动推断。
Q 什么时候需要用 move 关键字?
A 当你需要闭包拥有捕获变量的所有权时,特别是多线程场景。
Q 函数指针 fn(T) -> U 和闭包 trait Fn(T) -> U 可以互相替代吗?
A 不能完全替代。函数指针不能捕获环境,闭包可以。
Q 闭包作为参数时,impl FnBox<dyn Fn>、泛型 F: Fn 选哪个?
A 泛型约束 F: Fn 性能最好(静态分发),Box<dyn Fn> 最灵活(动态分发),impl Fn 是泛型的语法糖。
Q 闭包的性能如何?比普通函数慢吗?
A 不捕获环境的闭包和普通函数性能完全相同。捕获环境的闭包零开销或接近零开销。

📖 小节


📝 作业

  1. 难度 ⭐:有一个 Vec<i32> 包含 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]。写一个闭包捕获变量 threshold(设为 5),用 filter 筛选出大于 threshold 的数,用 map 将它们翻倍,最后 collect 到新的 Vec 中并打印。

  2. 难度 ⭐⭐:写一个函数 fn transform<F>(data: &[i32], f: F) -> Vec<i32> where F: Fn(i32) -> i32,对每个元素应用变换函数。在 main 中创建三个闭包:|x| x * 2(翻倍)、捕获变量 add 的加法闭包、捕获变量 max_val 的截断闭包(如果超过 max_val 就返回 max_val)。分别调用 transform 并打印结果。

  3. 难度 ⭐⭐⭐:有一个结构体 struct Product { name: String, price: f64, category: String }。创建 6 个产品实例放入 Vec。写一个函数 fn analyze_products<F1, F2>(products: &[Product], category_filter: F1, price_adjuster: F2) where F1: Fn(&&Product) -> bool, F2: Fn(f64) -> f64,先用 filter 筛选类别,再用 map 调整价格,最后按调整后的价格排序并打印前 3 个最贵的产品。在 main 中用不同的筛选和调价策略测试至少 2 次。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏