news 2026/9/11 18:16:47

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust入门知识点学与练】第17课:闭包 Closures

知识点1:闭包基本语法

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

fnmain(){// 完整写法letadd_one=|x:i32|->i32{x+1};println!("{}",add_one(5));// 6// 简写:类型可以省略(编译器会推断)letdouble=|x|x*2;println!("{}",double(5));// 10// 无参数letgreet=||println!("你好!");greet();// 多参数letadd=|a,b|a+b;println!("{}",add(3,4));// 7// 多行闭包需要花括号letprocess=|x:i32|{letdoubled=x*2;letresult=doubled+1;result};println!("{}",process(10));// 21}

知识点2:捕获环境变量

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

fnmain(){letname=String::from("Rust");// 1. 不可变借用 &(默认)letgreet=||println!("你好, {}",name);greet();println!("name 还能用: {}",name);// ✅ name 只是被借用// 2. 可变借用 &mutletmutcount=0;letmutincrement=||{count+=1;println!("计数: {}",count);};increment();// 计数: 1increment();// 计数: 2// println!("{}", count); // ❌ count 被可变借用中// 3. 获取所有权 moveletdata=vec![1,2,3];lettake_ownership=move||{println!("闭包拥有: {:?}",data);};take_ownership();// println!("{:?}", data); // ❌ data 的所有权已转移给闭包}

知识点3:闭包作为参数

// 接受闭包作为参数// Fn<F> 表示不可变借用环境的闭包fnapply<F>(f:F,value:i32)->i32whereF:Fn(i32)->i32,{f(value)}// FnMut<F> 表示可变借用环境的闭包fnapply_mut<F>(mutf:F,value:i32)->i32whereF:FnMut(i32)->i32,{f(value)}// FnOnce<F> 表示获取所有权的闭包(只能调用一次)fnapply_once<F>(f:F,value:i32)->i32whereF:FnOnce(i32)->i32,{f(value)}fnmain(){letresult=apply(|x|x*2,5);println!("{}",result);// 10letmuttotal=0;letresult=apply_mut(|x|{total+=x;total},5);println!("{}",result);// 5// move 闭包(获取所有权)letdata=String::from("hello");letresult=apply_once(move|x|{println!("data = {}, x = {}",data,x);x},42);println!("{}",result);// 42// println!("{}", data); // ❌ data 已被 move 进闭包}

知识点4:闭包返回闭包

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

// 方法1:用 impl Fn(推荐)fnmake_adder(x:i32)->implFn(i32)->i32{move|y|x+y// move 把 x 的所有权移入闭包}// 方法2:用 Box<dyn Fn>(需要动态分发)fnmake_multiplier(x:i32)->Box<dynFn(i32)->i32>{Box::new(move|y|x*y)}fnmain(){letadd_5=make_adder(5);println!("{}",add_5(10));// 15letmul_3=make_multiplier(3);println!("{}",mul_3(10));// 30}

知识点5:闭包与迭代器

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

fnmain(){letnumbers=vec![1,2,3,4,5,6,7,8,9,10];// map:对每个元素做变换letdoubled:Vec<i32>=numbers.iter().map(|x|x*2).collect();println!("翻倍: {:?}",doubled);// filter:过滤元素letevens:Vec<&i32>=numbers.iter().filter(|x|*x%2==0).collect();println!("偶数: {:?}",evens);// fold:累积计算letsum:i32=numbers.iter().fold(0,|acc,x|acc+x);println!("总和: {}",sum);// any / alllethas_even=numbers.iter().any(|x|x%2==0);letall_positive=numbers.iter().all(|x|*x>0);println!("有偶数: {}",has_even);println!("全正数: {}",all_positive);// findletfirst_big=numbers.iter().find(|&&x|x>7);println!("第一个大于7的: {:?}",first_big);// chain + 闭包leta=vec![1,2,3];letb=vec![4,5,6];letcombined: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()

动手试试

补全下面的代码:

// 补全:实现函数 apply_operations// 接受一个整数向量 vec 和一个闭包 operations// operations 接受一个 i32,返回一个 i32// 对 vec 中每个元素应用 operations,返回新的向量fnapply_operations<F>(vec:Vec<i32>,operations:F)->Vec<i32>whereF:Fn(i32)->i32,{// 补全:用 iter + map + collect 实现todo!()}// 补全:实现函数 make_counter// 返回一个闭包,每次调用时内部计数器加1并返回当前值// 初始值为 startfnmake_counter(start:i32)->implFnMut()->i32{// 补全:用 move 捕获一个可变计数器// 提示:需要一个 mut 变量在闭包内部todo!()}// 补全:实现函数 compose// 接受两个闭包 f 和 g,返回一个新的闭包// 新闭包对输入 x 先应用 g,再应用 f(即 f(g(x)))fncompose<F,G>(f:F,g:G)->implFn(i32)->i32whereF:Fn(i32)->i32,G:Fn(i32)->i32,{// 补全:返回一个闭包,先调用 g 再调用 ftodo!()}fnmain(){// 测试 apply_operationsletnums=vec![1,2,3,4,5];// 补全:对每个元素求平方letsquares=apply_operations(nums.clone(),|x|x*x);println!("平方: {:?}",squares);// 期望: [1, 4, 9, 16, 25]// 补全:对每个元素加10再乘2lettransformed=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。

// 对向量每个元素执行闭包变换fnapply_operations<F>(vec:Vec<i32>,operations:F)->Vec<i32>whereF:Fn(i32)->i32,{vec.into_iter().map(operations).collect()}// 返回一个带状态的计数器闭包(FnMut)fnmake_counter(start:i32)->implFnMut()->i32{letmutcount=start;move||{count+=1;count}}// 函数组合:compose(f,g) => f(g(x))fncompose<F,G>(f:F,g:G)->implFn(i32)->i32whereF:Fn(i32)->i32,G:Fn(i32)->i32,{move|x|f(g(x))}fnmain(){// 测试 apply_operationsletnums=vec![1,2,3,4,5];// 每个元素求平方letsquares=apply_operations(nums.clone(),|x|x*x);println!("平方: {:?}",squares);// 每个元素加10再乘2lettransformed=apply_operations(nums.clone(),|x|(x+10)*2);println!("变换: {:?}",transformed);// 测试 make_counter,从0开始letmutcounter=make_counter(0);println!("{}",counter());println!("{}",counter());println!("{}",counter());// 测试 compose:先 double,再 add_one => add_one(double(x))letdouble=|x|x*2;letadd_one=|x|x+1;letc1=compose(add_one,double);println!("compose1(5) = {}",c1(5));// 先 add_one,再 doubleletc2=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

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

区分:

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

2. make_counter

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

3. compose 函数组合

move|x|f(g(x))

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 18:16:27

顶空气体分析与残氧仪技术详解及应用

1. 顶空气体分析技术概述 在现代包装工业中&#xff0c;产品保质期的延长和品质保持是核心诉求。顶空气体分析技术&#xff08;Headspace Gas Analysis&#xff09;作为一种非破坏性检测方法&#xff0c;通过分析包装内部顶部空间的气体成分&#xff0c;为包装工艺优化和产品质…

作者头像 李华
网站建设 2026/9/11 18:16:01

垃圾短信识别实战:从TF-IDF到多模型对比的课程设计全解析

简介&#xff1a;这是一套面向网络数据挖掘课程设计/实训的垃圾短信识别系统完整工程&#xff0c;适合高校学生在毕业设计、课程设计、大作业、工程实训或学科竞赛中直接复现与二次开发。项目基于机器学习与自然语言处理实现短信文本分类&#xff0c;从数据预处理、特征工程到S…

作者头像 李华
网站建设 2026/9/11 18:14:50

如何三步跑通离线语音识别:Vosk 50MB 轻量模型完整指南

如何三步跑通离线语音识别&#xff1a;Vosk 50MB 轻量模型完整指南 【免费下载链接】vosk-api Offline speech recognition API for Android, iOS, Raspberry Pi and servers with Python, Java, C# and Node 项目地址: https://gitcode.com/GitHub_Trending/vo/vosk-api …

作者头像 李华
网站建设 2026/9/11 18:13:33

iphreeqc-py 0.1a6 安装与使用:用 Python 驱动 PHREEQC 水化学模拟

简介&#xff1a;iphreeqc-py 是一个面向 Python 开发者的第三方库安装包&#xff0c;专为地球化学模拟、水质分析以及相关科学计算场景设计。该库来自官方渠道&#xff0c;本质上是 PHREEQC 经典水文地球化学模拟引擎的 Python 接口封装&#xff0c;用户可以在熟悉的 Python 环…

作者头像 李华
网站建设 2026/9/11 18:12:14

燃气用户管理系统有哪些?核心功能与选型指南

燃气是城市能源供应体系的重要组成部分&#xff0c;覆盖居民生活与工业生产两大场景。随着管道燃气用户规模持续扩大&#xff0c;燃气企业既要在经营端完成数以万计的用户建档、计费与缴费服务&#xff0c;又要在安全端落实入户安检、隐患整改等主体责任&#xff0c;传统手工台…

作者头像 李华
网站建设 2026/9/11 18:11:16

北京GEO优化公司推荐:从ROI到长期资产

随着生成式AI搜索持续渗透&#xff0c;GEO已经成为北京企业构建品牌认知和获取精准流量的重要工作。北京服务商数量多、路线差异大&#xff0c;中小微企业常见问题是看不懂技术、容易被低价吸引、又担心大牌方案溢价过高。高性价比的判断&#xff0c;应回到投入产出&#xff0c…

作者头像 李华