【Rust自学】13.3. 闭包 Pt.3:使用泛型参数和fn trait来存储闭包

发布时间:2026/11/9 7:27:29

【Rust自学】13.3. 闭包 Pt.3:使用泛型参数和fn trait来存储闭包 13.3 闭包 Pt.3使用泛型参数和fn trait来存储闭包13.3.0. 写在正文之前Rust语言在设计过程中受到了很多语言的启发而函数式编程对Rust产生了非常显著的影响。函数式编程通常包括通过将函数作为值传递给参数、从其他函数返回它们、将它们分配给变量以供以后执行等等。在本章中我们会讨论 Rust 的一些特性这些特性与许多语言中通常称为函数式的特性相似-闭包本文- 迭代器- 使用闭包和迭代器改进I/O项目- 闭包和迭代器的性能13.3.1. 回顾还记得在 13.1 中的例子吗做一个程序根据人的身体指数等因素生成自定义的运动计划。这个程序的算法逻辑并不是重点重点是算法在计算过程中会花费几秒的时间。我们的目标是不让用户发生不必要的等待。具体来说就是仅在必要的时候才调用该算法而且只调用一次。当时我们修改代码为use std::thread; use std::time::Duration; fn main() { let simulated_user_specified_value 10; let simulated_random_number 7; generate_workout( simulated_user_specified_value, simulated_random_number, ); } fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure |num| { println!(calculating slowly...); thread::sleep(Duration::from_secs(2)); num }; if intensity 25 { println!(Today, do {} pushups!, expensive_closure(intensity)); println!(Next, do {} situps!, expensive_closure(intensity)); } else { if random_number 3 { println!(Take a break today! Remember to stay hydrated!); } else { println!(Today, run for {} minutes!, expensive_closure(intensity)); } } }但是还存在一个问题这么写没有解决闭包重复调用的问题。在intensity小于25的情况下调用了2次闭包。对于这个问题一个解决方案是把闭包的值赋给某个本地变量让这个本地变量被输出语句重复调用。这么写问题的是会造成一些代码的重复。所以这里更适合使用另一种解决方法创建一个结构体它持有闭包及其调用结果。也就是说在第一次调用闭包后把结果存到闭包持有者里如果以后还要调用闭包就直接使用缓存的结果。它的效果是只会在需要结果时才执行该闭包而且可缓存结果。这种模式通常叫记忆化memoization或延迟计算lazy evaluation13.3.2. 让结构体持有闭包根据刚才的解决方法目前的问题在于如何让结构体持有闭包。结构体的定义需要知道所有字段的类型所以如果想在结构体内存储闭包就必须指明闭包的类型。每个闭包实例都有自己唯一的匿名类型即使两个闭包签名完全一样这两个实例仍然是两个类型。所以存储闭包需要使用泛型以及trait bound泛型和trait bound的内容在 10.4. trait Pt.2 中有讲推荐看看这篇13.3.3.Fn traitFn trait由标准库提供。所有的闭包都至少实现了以下Fn trait之一-Fn-FnMut-FnOnce这三个Fn trait间的区别会在下一篇文章讲到。在本例中使用Fn就可以了。知道这些之后就可以修改例子了。首先创建一个结构体:struct CacheT: Fn(u32) - u32 { calculation: T, value: Optionu32, }这个结构体有一个泛型参数T由于它代表的是闭包的类型它的约束是Fntrait在本例中使用Fn就可以了,然后参数和返回值是u32所以写Fn(u32) - u32。闭包所在的字段是calculation它的类型就是T要缓存的值在value字段上其类型是u32但是要注意的是不清楚这个值是否已经计算出来并缓存在里面了所以要用Option类型来包裹也就是Optionu32。先在结构体上写一个构造函数用于创建实例implT: Fn(u32) - u32 CacheT { fn new(calculation: T) - CacheT { Cache { calculation, value: None, } } }这么写看着有点乱可以用where子句重写一下implT CacheT where T: Fn(u32) - u32 { fn new(calculation: T) - CacheT { Cache { calculation, value: None, } } }然后为了实现value有值就取value下的值value是None就计算的功能再写一个函数fn value(mut self, arg: u32) - u32 { match self.value { Some(v) v, None { let v (self.calculation)(arg); self.value Some(v); v } } }如果实例的value字段有值就返回这个值没有值就计算出这个值存储在value字段里再返回。这部分写好之后就该把generate_workout的写法改一下转为使用cache结构体fn generate_workout(intensity: u32, random_number: u32) { let mut expensive_closure Cache::new(|num|{ println!(calculating slowly...); thread::sleep(Duration::from_secs(2)); num }); if intensity 25 { println!(Today, do {} pushups!, expensive_closure.value(intensity)); println!(Next, do {} situps!, expensive_closure.value(intensity)); } else { if random_number 3 { println!(Take a break today! Remember to stay hydrated!); } else { println!(Today, run for {} minutes!, expensive_closure.value(intensity)); } } }把expensive_closure作为Cache结构体的实例使用new函数把闭包传进去。这里把expensive_closure加上mut设为可变绑定是因为后文调用时可能会改变value这个字段的值。下文所有要使用值的操作都使用value方法来获取。13.3.4. 使用缓存器实现的限制这里的Cache字段就是缓存器用于缓存某个值但这么写是有限制的。我把Cache的声明和其方法的代码贴在这里struct CacheT: Fn(u32) - u32 { calculation: T, value: Optionu32, } implT CacheT where T: Fn(u32) - u32 { fn new(calculation: T) - CacheT { Cache { calculation, value: None, } } fn value(mut self, arg: u32) - u32 { match self.value { Some(v) v, None { let v (self.calculation)(arg); self.value Some(v); v } } } }value这个方法总会得到同样的值如果value字段没有值那它就会计算出值然后把值存储在value字段里之后的其他地方使用value就会得到最开始的计算的这个值不论传进去的参数是什么。这么说可能有点模糊那来看个例子fn call_with_different_values(){ let mut c Cache::new(|a| a); let v1 c.value(1); let v2 c.value(2); }c是Cache的一个实例传进去了一个闭包。在let v1 c.value(1);这一行时原本c的value字段没有值这时候传进去个1value字段就变成Some(1)了(value字段是Option类型)在let v2 c.value(2);这一行时由于value字段原本有值所以会直接取value字段的1赋给v2即使这行的value方法的参数与上一行不一样。如果不想要这样就得使用HashMap来代替单个的值把HashMap的key作为value方法传进去的参数args而值就作为执行闭包的结果。比如说struct ForFunT: Fn(u32) - u32 { calculation: T, value: HashMapu32, Optionu32, } implT ForFunT where T: Fn(u32) - u32 { fn new(calculation: T) - ForFunT { ForFun { calculation, value: HashMap::new(), } } fn value(mut self, arg: u32) - u32 { match self.value.get(arg) { Some(v) v.unwrap(), None { let v (self.calculation)(arg); self.value.insert(arg, Some(v)); v } } } }这个例子中的缓存器只能接受同样的参数类型和返回值类型。如果想让闭包的参数类型和返回值类型不一样就可以引入两个及以上的泛型参数。比如说struct ForFunT, R where T: Fn(u32) - R, { calculation: T, value: OptionR, }
延伸阅读

更多相关文章

2026/11/8 22:57:14

FlexRay FIFO与消息处理机制深度解析:从双缓冲到实战配置

1. 项目概述:为什么需要深入理解FlexRay的FIFO与消息处理? 在汽车电子和工业控制领域,当工程师们谈论高可靠性的实时通信时,FlexRay协议是一个绕不开的名字。它被设计用来满足下一代汽车网络对带宽、确定性和容错性的苛刻要求&…

2026/11/9 7:27:26

【Rust自学】10.6. 生命周期 Pt.2:生命周期的语法与例子

10.6 生命周期 Pt.2:生命周期的语法与例子 10.6.1. 生命周期标注语法 标注生命周期并不会改变引用存活多久。如果某个函数指定了泛型生命周期参数,那么它可以接收带有任何生命周期的引用。生命周期标注主要用于描述多个引用的生命周期之间的关系&#…

2026/11/6 8:03:36

手把手教你搞定研0第一次组会

家人们,研0第一次组会是不是紧张到抠手?怕文献读不对,PPT做不好,一开口就挨批?我当时也是。后来回头想,组会其实没那么可怕,只是因为没人提前告诉我该准备什么。今天就手把手教你搞定研0第一次组…

2026/11/9 7:23:43

特种工档案都需要什么值不值得考

特种工档案都需要什么值不值得考 之前考的证在外省能不能转过来?这几乎是所有特种作业人员最关心的问题之一。尤其是像你这样已经在外地考了证,想回老家发展,又担心证书不被认可的,心里肯定打鼓。今天咱们就来聊清楚【特种工档案都需要什么】,顺便也帮你…

2026/11/9 7:23:43

汉川哪里有考电工证怎么报名别怕交了培训费考不过

汉川哪里有考电工证怎么报名别怕交了培训费考不过 怕考不过白交培训费?汉川哪里有考电工证怎么报名?别急,看完这篇,你就能轻松搞定!特种作业证考试不是走个形式,它关系到你以后的执业资格和安全责任。很多学员一上来就问“汉川哪里有考电工证”,其实更…

2026/11/9 7:23:43

江门电工证怎么办保姆级教程:外省证能不能转过来,看完你就明白了

江门电工证怎么办保姆级教程:外省证能不能转过来,看完你就明白了 之前考的证在外省能不能转过来?这几乎是每个准备考电工证或已经在外地考了证的人员最关心的问题。今天就从【江门电工证怎么办】这个角度,手把手教你怎么操作,外省证能不能转,能不能用,…

2026/11/9 7:23:43

漠河市电工证是真的吗网上查询学历不高也能报

漠河市电工证是真的吗网上查询学历不高也能报 你是不是也担心学历不高,报不了漠河市电工证?其实不是的,现在越来越多的政策开始向基层技能人才倾斜,只要你符合条件,不管学历高低,都可以报考。而且,这个证在 网上查询…

2026/11/9 7:18:43

彭水电工证通过率揭秘:学历不高怕报不上名?这样备考稳过!

彭水电工证通过率揭秘:学历不高怕报不上名?这样备考稳过! 你是不是也觉得,学历不高就考不了电工证?其实不然,彭水电工证的报考门槛并没有你想象中那么高。只要你具备一定的实践经验,通过系统复习和高效备考,完全有可能一次通过考试。今天我们就来聊聊…

2026/11/9 0:37:55

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/9 0:03:09

建设厅的特种工有哪些含金量高?别交完钱考不过

建设厅的特种工有哪些含金量高?别交完钱考不过 怕考不过白交培训费?别急,先搞清楚建设厅的特种工有哪些含金量高的证,再决定是不是值得投入时间和金钱。 建设厅的特种工证是建筑行业里最基础、也最实用的证书之一,涉及多个作业类别,每种证的 含金量…

2026/11/9 0:03:09

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱 怕考不过白交培训费?荆门市电工证报名前必须知道这些避坑指南。别看网上一堆培训机构广告,真要报个电工证,很多人被坑得一塌糊涂。特别是那些打着“包过”旗号的,最后不是考试不过,就是证不是全国通…

2026/11/9 0:03:09

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑 不知道去哪报名怕被中介坑?你不是一个人。现在特种作业证报名渠道五花八门,从线下培训机构到线上代理,真假难辨。尤其是像高空电工作业这类高风险、高门槛的作业类型,证书含金量和考试通过率直接影响你…

2026/11/9 6:30:27

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/7 14:50:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑