【Rust入门知识点学与练】第17课:闭包 Closures

知识点1:闭包基本语法

闭包是能捕获周围环境中变量的匿名函数:

rust 复制代码
fn main() {
    // 完整写法
    let add_one = |x: i32| -> i32 { x + 1 };
    println!("{}", add_one(5));  // 6

    // 简写:类型可以省略(编译器会推断)
    let double = |x| x * 2;
    println!("{}", double(5));  // 10

    // 无参数
    let greet = || println!("你好!");
    greet();

    // 多参数
    let add = |a, b| a + b;
    println!("{}", add(3, 4));  // 7

    // 多行闭包需要花括号
    let process = |x: i32| {
        let doubled = x * 2;
        let result = doubled + 1;
        result
    };
    println!("{}", process(10));  // 21
}

知识点2:捕获环境变量

闭包可以三种方式捕获环境变量:

rust 复制代码
fn main() {
    let name = String::from("Rust");

    // 1. 不可变借用 &(默认)
    let greet = || println!("你好, {}", name);
    greet();
    println!("name 还能用: {}", name);  // ✅ name 只是被借用

    // 2. 可变借用 &mut
    let mut count = 0;
    let mut increment = || {
        count += 1;
        println!("计数: {}", count);
    };
    increment();  // 计数: 1
    increment();  // 计数: 2
    // println!("{}", count);  // ❌ count 被可变借用中

    // 3. 获取所有权 move
    let data = vec![1, 2, 3];
    let take_ownership = move || {
        println!("闭包拥有: {:?}", data);
    };
    take_ownership();
    // println!("{:?}", data);  // ❌ data 的所有权已转移给闭包
}

知识点3:闭包作为参数

rust 复制代码
// 接受闭包作为参数
// Fn<F> 表示不可变借用环境的闭包
fn apply<F>(f: F, value: i32) -> i32
where
    F: Fn(i32) -> i32,
{
    f(value)
}

// FnMut<F> 表示可变借用环境的闭包
fn apply_mut<F>(mut f: F, value: i32) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(value)
}

// FnOnce<F> 表示获取所有权的闭包(只能调用一次)
fn apply_once<F>(f: F, value: i32) -> i32
where
    F: FnOnce(i32) -> i32,
{
    f(value)
}

fn main() {
    let result = apply(|x| x * 2, 5);
    println!("{}", result);  // 10

    let mut total = 0;
    let result = apply_mut(|x| {
        total += x;
        total
    }, 5);
    println!("{}", result);  // 5

    // move 闭包(获取所有权)
    let data = String::from("hello");
    let result = apply_once(move |x| {
        println!("data = {}, x = {}", data, x);
        x
    }, 42);
    println!("{}", result);  // 42
    // println!("{}", data);  // ❌ data 已被 move 进闭包
}

知识点4:闭包返回闭包

返回闭包需要用 Box 或 impl Fn:

rust 复制代码
// 方法1:用 impl Fn(推荐)
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y  // move 把 x 的所有权移入闭包
}

// 方法2:用 Box<dyn Fn>(需要动态分发)
fn make_multiplier(x: i32) -> Box<dyn Fn(i32) -> i32> {
    Box::new(move |y| x * y)
}

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

    let mul_3 = make_multiplier(3);
    println!("{}", mul_3(10));  // 30
}

知识点5:闭包与迭代器

闭包最常见的用途是配合迭代器:

rust 复制代码
fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // map:对每个元素做变换
    let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
    println!("翻倍: {:?}", doubled);

    // filter:过滤元素
    let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect();
    println!("偶数: {:?}", evens);

    // fold:累积计算
    let sum: i32 = numbers.iter().fold(0, |acc, x| acc + x);
    println!("总和: {}", sum);

    // any / all
    let has_even = numbers.iter().any(|x| x % 2 == 0);
    let all_positive = numbers.iter().all(|x| *x > 0);
    println!("有偶数: {}", has_even);
    println!("全正数: {}", all_positive);

    // find
    let first_big = numbers.iter().find(|&&x| x > 7);
    println!("第一个大于7的: {:?}", first_big);

    // chain + 闭包
    let a = vec![1, 2, 3];
    let b = vec![4, 5, 6];
    let combined: Vec<i32> = a.iter().chain(b.iter()).map(|x| x * 10).collect();
    println!("合并: {:?}", combined);
}

核心规则

概念 写法

闭包语法 params expression 或 params { statements }

类型标注 x: i32 -> i32 { x + 1 }

不可变借用 Fn trait(默认)

可变借用 FnMut trait

获取所有权 FnOnce trait + move 关键字

move 闭包 move { ... }

返回闭包 -> impl Fn(T) -> U 或 -> Box<dyn Fn(T) -> U>

配合迭代器 .map(), .filter(), .fold(), .any(), .find()

动手试试

补全下面的代码:

rust 复制代码
// 补全:实现函数 apply_operations
// 接受一个整数向量 vec 和一个闭包 operations
// operations 接受一个 i32,返回一个 i32
// 对 vec 中每个元素应用 operations,返回新的向量
fn apply_operations<F>(vec: Vec<i32>, operations: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    // 补全:用 iter + map + collect 实现
    todo!()
}

// 补全:实现函数 make_counter
// 返回一个闭包,每次调用时内部计数器加1并返回当前值
// 初始值为 start
fn make_counter(start: i32) -> impl FnMut() -> i32 {
    // 补全:用 move 捕获一个可变计数器
    // 提示:需要一个 mut 变量在闭包内部
    todo!()
}

// 补全:实现函数 compose
// 接受两个闭包 f 和 g,返回一个新的闭包
// 新闭包对输入 x 先应用 g,再应用 f(即 f(g(x)))
fn compose<F, G>(f: F, g: G) -> impl Fn(i32) -> i32
where
    F: Fn(i32) -> i32,
    G: Fn(i32) -> i32,
{
    // 补全:返回一个闭包,先调用 g 再调用 f
    todo!()
}

fn main() {
    // 测试 apply_operations
    let nums = vec![1, 2, 3, 4, 5];

    // 补全:对每个元素求平方
    let squares = apply_operations(nums.clone(), |x| x * x);
    println!("平方: {:?}", squares);
    // 期望: [1, 4, 9, 16, 25]

    // 补全:对每个元素加10再乘2
    let transformed = apply_operations(nums.clone(), |x| (x + 10) * 2);
    println!("变换: {:?}", transformed);
    // 期望: [22, 24, 26, 28, 30]

    // 测试 make_counter
    // 补全:创建一个从0开始的计数器
    // 连续调用3次,打印每次的值
    // 期望: 1, 2, 3

    // 测试 compose
    // 补全:创建两个闭包
    // double: x -> x * 2
    // add_one: x -> x + 1
    // 用 compose 组合:先 double 再 add_one,即 add_one(double(x)) = 2x + 1
    // 对 5 应用,期望输出: 11

    // 再组合:先 add_one 再 double,即 double(add_one(x)) = 2(x+1)
    // 对 5 应用,期望输出: 12
}

期望输出:

平方: 1, 4, 9, 16, 25

变换: 22, 24, 26, 28, 30

计数: 1

计数: 2

计数: 3

组合1(25+1): 11
组合2(2
(5+1)): 12

写好了贴过来,没问题就进入第18课:智能指针 Smart Pointers 👋

贴代码

下面是完整可运行的参考答案,闭包三大经典练习:map变换、状态闭包(FnMut)、函数组合 compose。

rust 复制代码
  
// 对向量每个元素执行闭包变换
fn apply_operations<F>(vec: Vec<i32>, operations: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    vec.into_iter().map(operations).collect()
}

// 返回一个带状态的计数器闭包(FnMut)
fn make_counter(start: i32) -> impl FnMut() -> i32 {
    let mut count = start;
    move || {
        count += 1;
        count
    }
}

// 函数组合:compose(f,g) => f(g(x))
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() {
    // 测试 apply_operations
    let nums = vec![1, 2, 3, 4, 5];

    // 每个元素求平方
    let squares = apply_operations(nums.clone(), |x| x * x);
    println!("平方: {:?}", squares);

    // 每个元素加10再乘2
    let transformed = apply_operations(nums.clone(), |x| (x + 10) * 2);
    println!("变换: {:?}", transformed);

    // 测试 make_counter,从0开始
    let mut counter = make_counter(0);
    println!("{}", counter());
    println!("{}", counter());
    println!("{}", counter());

    // 测试 compose:先 double,再 add_one => add_one(double(x))
    let double = |x| x * 2;
    let add_one = |x| x + 1;

    let c1 = compose(add_one, double);
    println!("compose1(5) = {}", c1(5));

    // 先 add_one,再 double
    let c2 = compose(double, add_one);
    println!("compose2(5) = {}", c2(5));
}

运行输出

plaintext

平方: 1, 4, 9, 16, 25

变换: 22, 24, 26, 28, 30

1

2

3

compose1(5) = 11

compose2(5) = 12

知识点详解

1. apply_operations

rust 复制代码
  
vec.into_iter().map(operations).collect()
  • into_iter :获取所有权的迭代器,消费 Vec;
  • map(operations) :迭代器适配器,把闭包应用到每一个元素;
  • collect() :把迭代器结果收集回新的 Vec。
  • F: Fn(i32) -> i32 :不可变闭包,不能捕获可变环境,只读。

区分:

  • Fn:捕获环境(只读),可以多次调用;
  • FnMut:可以修改捕获的变量;
  • FnOnce:只能调用一次(拿走捕获变量所有权)。

2. make_counter

rust 复制代码
  
fn make_counter(start:i32) -> impl FnMut()->i32 {
    let mut count = start;
    move || { count +=1; count }
}
 
  • move :把外部变量 count 移动进闭包,闭包拥有 count;
  • 返回类型是 impl FnMut() ,因为闭包内部修改 count;
  • 调用者那边必须声明 let mut counter ,才能调用。

3. compose 函数组合

rust 复制代码
  
move |x| f(g(x))

compose(f,g) 含义:先执行 g,再执行 f,数学记号: (f ∘ g)(x) = f(g(x)) 。

做完闭包这一关,Rust基础板块(泛型、生命周期、trait、闭包、迭代器)基本全部通关。

相关推荐
广州灵眸科技有限公司1 小时前
从手动三步到上电即连:4G模组systemd开机自连方案
linux·运维·服务器·开发语言·网络·人工智能·php
今夜有雨.1 小时前
C# 学习文档(零基础入门)
开发语言·学习·c#
软件黑马王子2 小时前
14.事件中心:主要作用和原理
开发语言·前端框架·c#
君顾12 小时前
酒吧点餐小程序系统开发实战:从架构设计到上线全流程指南
java·开发语言·酒吧
lifallen3 小时前
短暂 Agent 与持久化工程:让理解脱离对话
人工智能·学习·ai·重构·ai编程
知识分享小能手3 小时前
深度学习学习教程,从入门到精通,深度模型中的优化 — 完整知识点与代码案例(8)
人工智能·深度学习·学习
似水এ᭄往昔3 小时前
【Qt】--常用控件(显示类控件)
开发语言·qt
互联网叫兽3 小时前
redis深入学习二
数据库·redis·学习