rust学习-闭包

背景

模拟健康推荐算法,为前端提供高强度/低强度的训练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.mzph.cn/news/860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp实现微信小程序自带的分享功能

定义 share.js 文件 export default {data() {return {// 默认的全局分享内容share: {title: 标题,path: /pages/index/index, // 全局分享的路径imageUrl: , // 全局分享的图片(可本地可网络)}}},// 定义全局分享// 1.发送给朋友onShareAppMessage(res) {return {title: this…

数据结构与算法——希尔排序(引例、希尔增量序列、原始希尔排序、代码、时间复杂度、Hibbard增量序列、Sedgewick增量序列)

目录 引例 希尔增量序列 原始希尔排序 代码&#xff08;C语言&#xff09; 时间复杂度 更多增量序列 Hibbard增量序列 Sedgewick增量序列 希尔排序&#xff08;by Donald Shell&#xff09; 引例 给以下序列进行排序&#xff1a; 先以5的间隔进行插入排序&#xff1a…

设计模式之桥接模式

写在前面 本文看下桥接设计模式。 1&#xff1a;介绍 1.1&#xff1a;什么时候桥接设计模式 当一个业务场景由多个变化维度组成&#xff0c;并且这多个变化的维度到底有多少种情况是不确定&#xff0c;比如现在我们要为瑞幸咖啡写一个系统&#xff0c;很自然的&#xff0c;…

2023.7.16 第五十九次周报

目录 前言 文献阅读:跨多个时空尺度进行预测的时空 LSTM 模型 背景 本文思路 本文解决的问题 方法论 SPATIAL 自动机器学习模型 数据处理 模型性能 代码 用Python编写的LSTM多变量预测模型 总结 前言 This week, I studied an article that uses LSTM to solve p…

【前端知识】React 基础巩固(二十三)——React 性能优化 SCU相关

React 基础巩固(二十三)——React 性能优化 SCU React 更新机制 React 的渲染流程 JSX -> 虚拟 DOM -> 真实 DOM React 的更新流程 props/state 改变 -> render函数重新执行 -> 产生新的DOM树 -> 新旧DOM树进行diff -> 计算出差异进行更新 -> 更新到真实…

Element分页组件自定义样式

样式效果 页面代码 <el-paginationsize-change"handleSizeChange"current-change"handleCurrentChange":current-page"page.page":page-sizes"[10, 20, 30, 40]":page-size"page.size"layout"total, sizes, prev, …

如何用https协议支持小程序

步骤一&#xff1a;下载SSL证书 登录数字证书管理服务控制台。在左侧导航栏&#xff0c;单击SSL 证书。在SSL证书页面&#xff0c;定位到目标证书&#xff0c;在操作列&#xff0c;单击下载。 在服务器类型为Nginx的操作列&#xff0c;单击下载。 解压缩已下载的SSL证书压缩…

Python中Threading对于TCP的使用

什么是多线程&#xff1f; 多线程类似于同时执行多个不同程序&#xff0c;多线程运行有如下优点&#xff1a; 使用线程可以把占据长时间的程序中的任务放到后台去处理。 用户界面可以更加吸引人&#xff0c;这样比如用户点击了一个按钮去触发某些事件的处理&#xff0c;可以弹…

springboot经典面试题2

问题&#xff1a;什么是Spring Boot的起步依赖&#xff08;Starter Dependencies&#xff09;&#xff1f;如何使用起步依赖&#xff1f; 答案&#xff1a;Spring Boot的起步依赖是一组预定义的依赖项&#xff0c;可以简化项目的配置和构建过程。可以通过在项目的构建配置文件…

[个人笔记] WinSrv批量添加DNS记录和条件转发器记录

Windows Server - 运维篇 第三章 WinSrv批量添加DNS记录和条件转发器记录 Windows Server - 运维篇系列文章回顾WinSrv批量添加DNS记录和条件转发器记录单条记录添加DNS条件转发器记录批量新增DNS条件转发器记录批量导出DNS条件转发器 参考来源 系列文章回顾 第一章 域控使用C…

使用 jmeter 进行审批类接口并发测试

目录 前言&#xff1a; 背景&#xff1a; 难点&#xff1a; 场景 a&#xff1a; 场景 b&#xff1a; 前言&#xff1a; 使用JMeter进行审批类接口的并发测试是一种有效的方法&#xff0c;可以模拟多个用户同时对接口进行审批操作&#xff0c;以评估系统在高负载情况下的性…

Java+Vue+Uniapp全端WMS仓库管理系统

详情图片为运行截图,功能列表: 1、数据管理:物料数据管理、物料Bom管理、物料组管理、物料分类管理、供应商管理、仓库管理、货位管理、车间管理 2、采购管理:物料标签管理、入库单管理、入库退货管理 3、质检管理:质检单管理(包括单据号、单据类型、创建时间、检验状态…

4. CSS用户界面样式

4.1什么是界面样式 所谓的界面样式,就是更改一些用户操作样式,以便提高更好的用户体验。 ●更改用户的鼠标样式 ●表单轮廓 ●防止表单域拖拽 4.2鼠标样式cursor li {cursor: pointer; }设置或检索在对象上移动的鼠标指针采用何种系统预定义的光标形状。 4.3轮廓线outline…

Mac如何将homebrew添加到系统路径

1.在终端下进入路径配置文件 sudo vim .zprofile 2.输入用户密码进入文本&#xff0c;键盘按i进入编辑模式&#xff08;左下角会显示INSERT状态&#xff09;&#xff0c;配置环境变量&#xff1a; export PATH/opt/homebrew/bin:$PATH3.Esc退出编辑模式&#xff0c;输入**&am…

排序算法第三辑——交换排序

目录 ​编辑 一&#xff0c;交换排序算法的简介 二&#xff0c;冒泡排序 冒泡排序代码&#xff1a;排升序 三&#xff0c;快速排序 1.霍尔大佬写的快速排序 2.挖坑法 3.前后指针法 四&#xff0c;以上代码的缺陷与改正方法 三数取中 三路划分&#xff1a; 五&#…

【电子学会】2023年05月图形化四级 -- 计算圆的面积和周长

计算圆的面积和周长 编写程序计算圆的面积和周长。输入圆的半径&#xff0c;程序计算出圆的面积和周长&#xff0c;圆的面积等于3.14*半径*半径&#xff1b;圆的周长等于2*3.14*半径。 1. 准备工作 &#xff08;1&#xff09;保留舞台中的小猫角色和白色背景&#xff1b; 2…

Stream流式思想

什么是Stream&#xff1f; 也叫Stream流&#xff0c;是Jdk8开始新增的一套API (java.util.stream.*)&#xff0c;可以用于操作集合或者数组的数据。 优势&#xff1a; Stream流大量的结合了Lambda的语法风格来编程&#xff0c;提供了一种更加强大&#xff0c;更加简单的方式操…

从 Blender 导出动画视频

Blender Tutorial - How To Export Video Files 要导出编辑完的视频&#xff0c;你需要先设置好输出的格式、分辨率、帧率、位置等参数&#xff0c;然后选择渲染动画的选项&#xff0c;等待渲染完成后&#xff0c;你就可以在指定的位置找到生成的视频文件。 具体步骤如下&…

Python 列表 sort()函数使用详解

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;小白零基础《Python入门到精通》 sort函数使用详解 1、升序降序2、sort()和sorted()的区别3、切片排序4、指定排序…

vue封装短信验证码,刷新缓存倒计时

HTML代码 <a-form-item field"verifyCode" label"验证码:"><a-input v-model"formData.verifyCode" placeholder"短信验证码" class"login-form-button" allow-clear><template #append><span:class&q…