rust学习-闭包

news/2024/12/2 10:32:51/

背景

模拟健康推荐算法,为前端提供高强度/低强度的训练app

use std::thread;
use std::time::Duration;fn simulated_expensive_calculation(intensity: u32) -> u32 {println!("calculating slowly...");thread::sleep(Duration::from_secs(2));intensity
}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) {if intensity < 25 {println!("Today, do {} pushups!",simulated_expensive_calculation(intensity));// 这里想直接拿结果,但是依旧需要重新计算// 解决办法在后文println!("Next, do {} situps!",simulated_expensive_calculation(intensity));} else {if random_number == 3 {println!("Take a break today! Remember to stay hydrated!");} else {println!("Today, run for {} minutes!",simulated_expensive_calculation(intensity));}}
}

simulated_expensive_calculation 是个算法模块维护的内容,且未来变化较大,所以代码中期待对其只使用一次。

用闭包重构

use std::thread;
use std::time::Duration;fn generate_workout(intensity: u32, random_number: u32) {// 闭包的定义以一对竖线(|)开始,在竖线中指定闭包的参数// 如果有多于一个参数,可以使用逗号分隔,比如 |param1, param2|// 如果闭包体只有一行则大括号是可以省略// let 语句意味着 expensive_closure 包含一个匿名函数的 定义// 不是调用匿名函数的 返回值let expensive_closure = |num| {println!("calculating slowly...");thread::sleep(Duration::from_secs(2));// 闭包体的最后一行没有分号(正如函数体一样)// 所以闭包体(num)最后一行的值作为调用闭包时的返回值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));}}
}fn main() {let simulated_user_specified_value = 10;let simulated_random_number = 7;generate_workout(simulated_user_specified_value,simulated_random_number);
}

闭包类型推断和标注

闭包不要求像 fn 函数那样在参数和返回值上注明类型
函数需要类型标注是因为是暴露给用户的显式接口的一部分
如果一定要类型标注,也不是不可以

fn  add_one_v1   (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 }; // 类型标注
let add_one_v3 = |x|             { x + 1 };
let add_one_v4 = |x|               x + 1  ;

闭包定义会为每个参数和返回值推断一个具体类型

let example_closure = |x| x;
let s = example_closure(String::from("hello"));
// 编译器推断 x 和此闭包返回值的类型为 String
// 这些类型被锁定进闭包 example_closure 中
// 如果尝试对同一闭包使用不同类型则会得到类型错误
// 如下编译则报错
// let n = example_closure(5);

使用带有泛型和 Fn trait 的闭包

解决在一个上下文中,闭包被重复计算的问题
方法一:
创建一个存放闭包和调用闭包结果的变量(不同的上下文可能变量会很多)
方法二:
创建一个存放闭包和调用闭包结果的结构体
该结构体只会在需要结果时执行闭包,并会缓存结果值,即缓存

lazy evaluation (惰性求值)

// 定义一个 Cacher 结构体来在 calculation 中存放闭包
// 在 value 中存放 Option 值
// 结构体中存储闭包类型:闭包有一个 u32 的参数并返回一个 u32
// 这样所指定的 trait bound 就是 Fn(u32) -> u32
// 所有的闭包都实现了 trait Fn、FnMut 或 FnOnce 中的一个,这里使用 trait Fn
//
// 注意:函数也都实现了这三个 Fn trait
// 如果不需要捕获环境中的值,则可以使用实现了 Fn trait 的函数而不是闭包
//
// 在执行闭包之前,value 将是 None
// 如果使用 Cacher 的代码请求闭包的结果,这时会执行闭包并将结果储存在 value 字段的 Some 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 Some 成员中的结果
use std::thread;
use std::time::Duration;struct Cacher<T>where T: Fn(u32) -> u32
{// Cacher 结构体的字段是私有的,代表着仅由Cacher的方法管理它们calculation: T,value: Option<u32>,
}impl<T> Cacher<T>where T: Fn(u32) -> u32
{// Cacher::new 获取一个泛型参数 T,和结构体有着相同的trait boundfn new(calculation: T) -> Cacher<T> {Cacher {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); // 将结果存储到Some中v},}}
}fn generate_workout(intensity: u32, random_number: u32) {let mut expensive_result = Cacher::new(|num| {println!("calculating slowly...");thread::sleep(Duration::from_secs(2));num});if intensity < 25 {println!("Today, do {} pushups!",expensive_result.value(intensity));println!("Next, do {} situps!",expensive_result.value(intensity));} else {if random_number == 3 {println!("Take a break today! Remember to stay hydrated!");} else {println!("Today, run for {} minutes!",expensive_result.value(intensity));}}
}fn main() {let simulated_user_specified_value = 10;let simulated_random_number = 7;generate_workout(simulated_user_specified_value,simulated_random_number);
}

该cacher的缺点
(1)Cacher 实例假设对于 value 方法的任何 arg 参数值总是会返回相同值

#[test]
fn call_with_different_values() {let mut c = Cacher::new(|a| a);let v1 = c.value(1); // 此时缓存中的值是1let v2 = c.value(2); // 此时缓存中的值还是1assert_eq!(v2, 2);
}

解决办法:Cacher 存放哈希 map 而不是单独一个值
哈希 map 的 key 将是传递进来的 arg 值
哈希 map 的 value 则是对应 key 调用闭包的结果值
value 函数会在哈希 map 中寻找 arg,如果找到的话就返回其对应的值。
如果不存在,Cacher 会调用闭包并将结果值保存在哈希 map 对应 arg 值的位置。

(2)不够泛型
它的应用被限制为只接受获取一个 u32 值并返回一个 u32 值的闭包
如果需要能够缓存一个获取字符串 slice 并返回 usize 值的闭包的结果呢?

闭包捕获其环境

闭包还有另一个函数所没有的功能:
他们可以捕获其环境并访问其被定义的作用域的变量

当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。
这会使用内存并产生额外的开销

fn main() {let x = 4;// 闭包中直接使用变量x,因为它与 equal_to_x 定义于相同的作用域// 函数则不行let equal_to_x = |z| z == x;let y = 4;assert!(equal_to_x(y));
}

捕获环境的三种方式:

闭包周围的作用域被称为其环境,environment
对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用
创建一个闭包时,Rust 根据其如何使用环境中变量来推断如何引用环境。

FnOnce

为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。
其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权,所以它只能被调用一次。

FnMut

获取可变的借用值,可以改变其环境

Fn

从其环境获取不可变的借用值

fn main() {// 整型可以被拷贝而不是移动,所以这里采用veclet x = vec![1, 2, 3];// 强制闭包获取其使用的环境值的所有权// 闭包获取了 x 的所有权let equal_to_x = move |z| z == x;// 这里x无法打印,编译报错// println!("can't use x here: {:?}", x);let y = vec![1, 2, 3];assert!(equal_to_x(y));
}

http://www.ppmy.cn/news/917562.html

相关文章

用python计算黄历

黄历是指中国传统文化中使用的一种历法。在 Python 中&#xff0c;你可以使用第三方库来计算黄历。 其中一个可以使用的库是 lunar_calendar。你可以使用 pip install lunar_calendar 来安装这个库。 然后&#xff0c;你就可以使用下面的代码来计算指定日期的黄历信息了&#x…

【Python】ChineseCalendar包简介

ChineseCalendar 是一个 Python 包&#xff0c;用于获取中国传统日历信息。这个包提供了中国农历、二十四节气、传统节日、黄历等信息。你可以使用以下命令来安装这个包&#xff1a; pip install ChineseCalendar安装完成后&#xff0c;你可以在你的 Python 代码中导入该包并使…

公农历转换

********************************************************************************************建一个类&#xff0c;类名为clsDateOption ExplicitDim ZDay As Long /// // // // …

万年日历

API接口&#xff1a; https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query2019年04月&resource_id6018&formatjson # codinggbk import requests from bs4 import BeautifulSoup import xlwt # 获取一年数据&#xff0c;以字典返回 def getYear(): ye…

老黄历用语解释

老黄历用语解释 ●今日吉日 ○吉时&#xff1a; 吉利之时辰 ○凶时&#xff1a; 凶兆之时辰 ●今日宜忌 ○彭祖百忌&#xff1a; 每日时辰应忌讳之事 ○吉神宜趋&#xff1a; 宜接近&#xff0c;会有吉利的神明 ○宜&#xff1a; 今日适合做的事情 ○凶神宜忌&#xff1a; 应远…

解读老黄历--月日时令

解读老黄历--月日时令 每月包含内容&#xff1a; 月历内容包括&#xff1a;月之大小&#xff1b;月建&#xff08;1989年己巳十二月为例&#xff0c;月建丁丑&#xff09;&#xff1b;交节日时及本月起止&#xff1b;当月所值月宿&#xff08;1989年十二月为例&#xff0c;月宿…

2020年黄历表_老黄历2020黄道吉日一览表-万年历老黄历2020年黄道吉日查询【蜜匠婚礼】...

结婚吉日是新人新人的祝福,结婚黄道吉日可以使得新人婚后相处美满融洽。下面我们就一起来看看老黄历2020黄道吉日一览表,万年历老黄历2020年黄道吉日查询。 2020年黄道吉日一览表 2020年1月黄道吉日共12天 公元2020年1月02日,星期四,农历二零一九年十二月(大)初八,冲狗(戊戍…

专业黄历顺历传承历法,首部万年历记录片上线

中国的传统历法相传最早诞生在黄帝时期&#xff0c;具体时间已无从考证&#xff0c;但根据出土的甲骨文和古代典籍&#xff0c;一般认为现在的历法规则源自殷商时期&#xff0c;距今已有四千多年的历史。而在这个万物互联&#xff0c;一切智能化的时代&#xff0c;以专业黄历著…