【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
系列文章目录
【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、闭包(Closures)
- 1.1、闭包(Closures)
- 1.2、Rust的闭包语法
- 1.2.1、闭包语法形式
- 1.2.1、闭包简化形式
- 1.3、闭包的类型推导
- 1.4、结构体中的闭包
- 1.5、捕获环境中的值
- 1.5.1、捕获环境的值
- 1.5.2、闭包对内存的影响
- 1.5.2、三种 Fn trait
- 1.5.2.1、FnOnce
- 1.5.2.2、FnMut
- 1.5.2.3、Fn Trait
- 1.5.3、move 和 Fn
- 1.5.4、三种 Fn 的关系
- 1.6、闭包作为函数返回值
- 二、迭代器(Iterators)
- 2.1、迭代器(Iterators)
- 2.2、Iterator trait
- 2.2.1、Iterator trait
- 2.2.2、next 方法
- 2.3、迭代方法
- 2.4、消耗迭代器的方法
- 2.4.1、sum 方法
- 2.4.2、collect方法
- 2.5、迭代器适配器
- 2.5.1、map
- 2.5.2、zip
- 2.5.3、filter
- 2.5.4、enumerate
- 2.6、自定义迭代器
- 四、性能对比:循环 VS 迭代器
- 总结
文章目录
- 系列文章目录
- @[TOC](文章目录)
- 前言
- 一、闭包(Closures)
- 1.1、闭包(Closures)
- 1.2、Rust的闭包语法
- 1.2.1、闭包语法形式
- 1.2.1、闭包简化形式
- 1.3、闭包的类型推导
- 1.4、结构体中的闭包
- 1.5、捕获环境中的值
- 1.5.1、捕获环境的值
- 1.5.2、闭包对内存的影响
- 1.5.2、三种 Fn trait
- 1.5.2.1、FnOnce
- 1.5.2.2、FnMut
- 1.5.2.3、Fn Trait
- 1.5.3、move 和 Fn
- 1.5.4、三种 Fn 的关系
- 1.6、闭包作为函数返回值
- 二、迭代器(Iterators)
- 2.1、迭代器(Iterators)
- 2.2、Iterator trait
- 2.2.1、Iterator trait
- 2.2.2、next 方法
- 2.3、迭代方法
- 2.4、消耗迭代器的方法
- 2.4.1、sum 方法
- 2.4.2、collect方法
- 2.5、迭代器适配器
- 2.5.1、map
- 2.5.2、zip
- 2.5.3、filter
- 2.5.4、enumerate
- 2.6、自定义迭代器
- 四、性能对比:循环 VS 迭代器
- 总结
前言
Rust的设计灵感来源于很多现存的语言和技术,其中一个显著的影响就是函数式编程(functional prigramming)。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将韩式赋值给变量以供之后执行等等。
主要教材参考 《The Rust Programming Language》
一、闭包(Closures)
1.1、闭包(Closures)
Rust的闭包(Closures)是可以保存进变量或作为参数传递给其他函数的匿名函数。可以在一个地方创建闭包,然后在不同的上下文执行闭包运算。不同函数,闭包允许捕获调用作用域中的值。
例如:
- 使用函数作为参数进行传递
- 使用函数作为函数返回值
- 将函数赋值给变量
1.2、Rust的闭包语法
1.2.1、闭包语法形式
Rust闭包在形式上借鉴了 Smalltalk 和 Ruby 语言,与函数最大的不同就是它的参数是通过 |param| 的形式进行声明的。
示例:闭包语法形式
|param1,param2,....|{语句1;语句2;...返回表达式
}
1.2.1、闭包简化形式
如果只有一个返回表达式可以简化成如下形式
|param|返回表达式
1.3、闭包的类型推导
Rust 是静态语言,因此所有的变量都具有类型,但是得益于编译器的强大类型推导能力,在很多时候我们并不需要显式声明类型,但是函数和必须为所有参数和返回值指定类型。
为了增加代码可读性,有时候我们会显式地给类型进行标注,出于同样的目的,也可以给闭包标注类型。
let sum = |x:i32, y:32| -> 32{x + y
}
类型推导虽然很好用,但是它不是泛型,当编译器推导出一种类型后,它就会一直使用该类型。
1.4、结构体中的闭包
struct Cacher<T> where T: Fn(u32) -> u32 {query: T,value: Optional<u32>
}
此时 query 就是一个闭包,他的类型是 Fn(u32) -> u32 是一个特征,用来表示T 是一个闭包类型。
1.5、捕获环境中的值
1.5.1、捕获环境的值
闭包可以捕获环境中的值
fn main() {let x = 4;let equal_to_x = |z| z == x;let y = 4;assert!(equal_to_x(y));
}
如果你使用函数来实现,编译器会告诉我们无法捕获动态环境中的值。
1.5.2、闭包对内存的影响
当闭包从环境中捕获一个值时,会分配内存去存储这些值。对于有些场景来说,这种额外的内存分配会成为一种负担。与之相比,函数就不会去捕获这些环境值,因此定义和使用函数不会拥有这种内存负担。
1.5.2、三种 Fn trait
闭包捕获环境变量有三种途径,恰好对应函数参数的三种传入方式:转移所有权、可变借用、不可变借用,因此Fn Trait 也有三种。
1.5.2.1、FnOnce
该类型的闭包会拿走被捕获变量的所有权。该闭包只能运行一次。
fn fn_once<F>(func: F)
whereF: FnOnce(usize) -> bool,
{println!("{}", func(3));println!("{}", func(4));
}fn main() {let x = vec![1, 2, 3];fn_once(|z|{z == x.len()})
}
此时编译器就会报错,因为不能对已失去所有权的闭包变量进行二次调用。 错误提示告诉我们因为F没有实现Copy Trait ,所以会报错,那么我们添加约束,试试实现了Copy的闭包。
fn fn_once<F>(func: F)
whereF: FnOnce(usize) -> bool + Copy,// 改动在这里
{println!("{}", func(3));println!("{}", func(4));
}fn main() {let x = vec![1, 2, 3];fn_once(|z|{z == x.len()})
}
如果你想强制闭包取得捕获变量的所有权,那么可以在参数列表前面添加 move 关键字,这种用法通常用在闭包的生命周期大于捕获变量的生命周期时,例如将闭包返回或移入其他线程。
use std::thread;
let v = vec![1, 2, 3];
let handle = thread::spawn(move || {println!("Here's a vector: {:?}", v);
});
handle.join().unwrap();
1.5.2.2、FnMut
它是可变借用的方式捕获了环境中的值,因此可以修改该值。
fn main() {let mut s = String::new();let mut update_string = |str| s.push_str(str);update_string("hello");println!("{:?}",s);
}
复杂形式
fn main() {let mut s = String::new();let update_string = |str| s.push_str(str);exec(update_string);println!("{:?}",s);
}fn exec<'a, F: FnMut(&'a str)>(mut f: F) {f("hello")
}
1.5.2.3、Fn Trait
它是以不可变借用的方式捕获环境中的值,让我们把上面的代码中的 F 类型修改为Fn。
fn main() {let mut s = String::new();let update_string = |str| s.push_str(str);exec(update_string);println!("{:?}",s);
}fn exec<'a, F: Fn(&'a str)>(mut f: F) {f("hello")
}
从报错中很清晰的看出,我们的闭包实现的是 FnMut 特征,需要的是可变借用,但是在 exec 中却给它标注了 Fn 特征,因此产生了不匹配,再来看看正确的不可变借用方式:
fn main() {let s = "hello, ".to_string();let update_string = |str| println!("{},{}",s,str);exec(update_string);println!("{:?}",s);
}fn exec<'a, F: Fn(String) -> ()>(f: F) {f("world".to_string())
}
1.5.3、move 和 Fn
在上面我们讲解到 move 关键字对于 FnOnce的重要性,实际上使用了 move 的闭包依然可能实现了 Fn 和
Fn Mut 特征。
因为一个闭包实现了哪种 Fn 特征取决于该闭包如何使用被捕获的变量,而不是取决于闭包如何捕获它们。move 本身强调的就是后者,闭包如何捕获变量:
1.5.4、三种 Fn 的关系
实际上,一个闭包不仅仅实现一种 Fn trait,规则如下
- 所有的闭包都自动实现了 FnOnce 特征,因此任何一个闭包都至少可以被调用一次
- 没有移出所有捕获变量的所有权的闭包自动实现了 FnMut Trait
- 不需要对捕获变量进行改变的闭包自动失效了 Fn Trait
pub trait Fn<Args> : FnMut<Args> {extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}pub trait FnMut<Args> : FnOnce<Args> {extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}pub trait FnOnce<Args> {type Output;extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}
1.6、闭包作为函数返回值
fn factory(x:i32) -> Box<dyn Fn(i32) -> i32> {let num = 5;if x > 1{Box::new(move |x| x + num)} else {Box::new(move |x| x - num)}
}
二、迭代器(Iterators)
2.1、迭代器(Iterators)
迭代器模式:对一系列项执行某些人物,迭代器负责遍历每一项,确定序列何时完成。Rust的迭代器是惰性的,除非调用消费迭代器的方法,否则迭代器本身没有任何效果。
迭代器允许我们迭代一个连续的集合,例如数组、向量、hashMap等。
let v1 = vec![1, 2, 3];let v1_iter = v1.iter();for val in v1_iter {println!("{}", val);
}
2.2、Iterator trait
2.2.1、Iterator trait
所有的迭代器都实现了 Iterator trait,该 Trait 定义于标准库。定义大致如下。
pub trait Iterator {type Item;fn next(&mut self) -> Option<Self::Item>;// 省略其余有默认实现的方法
}
2.2.2、next 方法
每次返回迭代器中的一项,返回结果包裹在Some 里面,迭代结束返回None。
fn main() {let arr = [1, 2, 3];let mut arr_iter = arr.into_iter();assert_eq!(arr_iter.next(), Some(1));assert_eq!(arr_iter.next(), Some(2));assert_eq!(arr_iter.next(), Some(3));assert_eq!(arr_iter.next(), None);
}
2.3、迭代方法
iter:在不可变引用上创建迭代器
into_iter: 创建的迭代器会获得所有权
iter_mut: 迭代可变的引用
2.4、消耗迭代器的方法
2.4.1、sum 方法
调用 next 方法的方法叫做 消耗型适配器。 例如 :Sum 方法:取得迭代器的所有权。
fn main() {let v1 = vec![1, 2, 3];let v1_iter = v1.iter();let total: i32 = v1_iter.sum();assert_eq!(total, 6);// v1_iter 是借用了 v1,因此 v1 可以照常使用println!("{:?}",v1);// 以下代码会报错,因为 `sum` 拿到了迭代器 `v1_iter` 的所有权// println!("{:?}",v1_iter);
}
2.4.2、collect方法
use std::collections::HashMap;
fn main() {let names = ["sunface", "sunfei"];let ages = [18, 18];let folks: HashMap<_, _> = names.into_iter().zip(ages.into_iter()).collect();println!("{:?}",folks);
}
zip 是一个迭代器适配器,它的作用就是将两个迭代器的内容压缩到一起,形成 Iterator<Item=(ValueFromA, ValueFromB)> 这样的新的迭代器,在此处就是形如 [(name1, age1), (name2, age2)] 的迭代器。
然后再通过 collect 将新迭代器中(K, V) 形式的值收集成 HashMap<K, V>,同样的,这里必须显式声明类型,然后 HashMap 内部的 KV 类型可以交给编译器去推导,最终编译器会推导出 HashMap<&str, i32>,完全正确!
2.5、迭代器适配器
2.5.1、map
既然消费者适配器是消费掉迭代器,然后返回一个值。那么迭代器适配器,顾名思义,会返回一个新的迭代器,这是实现链式方法调用的关键:v.iter().map().filter()…。
与消费者适配器不同,迭代器适配器是惰性的,意味着你需要一个消费者适配器来收尾,最终将迭代器转换成一个具体的值:
let v1: Vec<i32> = vec![1, 2, 3];let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();assert_eq!(v2, vec![2, 3, 4]);
2.5.2、zip
2.5.3、filter
2.5.4、enumerate
使用 enumerate 方法可以获取迭代时的索引。
let v = vec![1u64, 2, 3, 4, 5, 6];
for (i,v) in v.iter().enumerate() {println!("第{}个值是{}",i,v)
}
2.6、自定义迭代器
自定义迭代器很简单,我们只需要实现 Iterator 特征 以及next 方法即可。实际上 Iterator 之中还有其他方法,其他方法都有默认实现,无需手动去实现。
impl Iterator for Counter {type Item = u32;fn next(&mut self) -> Option<Self::Item> {if self.count < 5 {self.count += 1;Some(self.count)} else {None}}
}
四、性能对比:循环 VS 迭代器
迭代器是 Rust的零成本抽象(zero-cost abstractions) 之一,意味着抽象并不会引入运行时开销。
总结
以上就是今天要讲的内容
相关文章:

【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…...

时间复杂度和空间复杂度
全文目录 算法的复杂度时间复杂度大O渐进表示法空间复杂度常见算法复杂度对比 算法的复杂度 算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此衡量一个算法的好坏,一般是从时间和空间两个维度来衡量的,即时间复杂度…...

mac docker 卡住解决
文章目录 1、问题简述2、重新安装docker3、docker守护进程4、问题解决方案 1、问题简述 在docker desktop上更改了daemon.json的文件内容,应该是参数写的有问题,修改完配置再启动docker desktop就失败了,然后想着卸载docker desktop…...

linux/centos zookeeper 使用记录
配置cfg 下载zookeeper-3.4.14.tar.gz负责到centos服务器解压 /xxx/zookeeper-3.4.14/conf/下创建zoo.cfg文件并配置以下属性,/bsoft/zookeeperdata/目录先预先创建 tickTime2000 initLimit10 syncLimit5 dataDir/bsoft/zookeeperdata/ clientPort2181zk启动/重启/关…...

用wireshark流量分析的四个案例
目录 第一题 1 2 3 4 第二题 1 2 3. 第三题 1 2 第四题 1 2 3 第一题 题目: 1.黑客攻击的第一个受害主机的网卡IP地址 2.黑客对URL的哪一个参数实施了SQL注入 3.第一个受害主机网站数据库的表前缀(加上下划线例如abc) 4.…...

Oracle 时区详解
1 简介 由于地球经纬度及地球自转引起的经度方向,不同的经度的地方,所感受到的昼夜是不同 的。有关国际会议决定将地球表面按经线从东到西,每隔经度15度划分一个时区,并且规定 相邻区域的时间相差1小时。 这就是时区的由来。 而实际使用中,…...

仿mudou高性能高并发服务器
"这个结局是我的期待,我会一直为你祝福。" 项目实现目标: 仿muduo库One Thread One Loop式主从Reacto模型实现高并发服务器。通过实现高并发服务器组件,简洁快速完成搭建一个高性能服务器。并且,通过组件内提供的不同应⽤层协议⽀…...

vue权限管理——菜单权限设置
1.前提:后端提供菜单对应数据 此处用mockjs模拟 const menuList [{id: 1, path:/uploadSpec,authName: "上传spec", icon: User, children:[], rights:[view,add,edit,delete]},{id: 2, path:/showSpec, authName: "Spec预览", icon: DataAn…...

【LeetCode】228.汇总区间
题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说,nums 的每个元素都恰好被某个区间范围所覆盖,并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b]…...

Qt快速学习(二)--QMainWindow,对话框,布局管理器,常用控件
目录 1 QMainWindow 1.1 菜单栏 1.2 工具栏 1.3 状态栏 1.4 铆接部件 1.5 核心部件(中心部件) 1.6 资源文件 2 对话框QDialog 2.1 基本概念 2.2 标准对话框 2.3 自定义消息框 2.4 消息对话框 2.5 标准文件对话框 3 布局管理器 3.1 系统…...

群晖DSM下套件及系统网页服务器ssl证书自动更新
关键字: DSM ssl 证书 起因 群晖下自建服务(alist3)和系统服务在外部网络访问需要加ssl安全证书来实现基础的传输保护。 申请证书和续期手动操作都还好,不算太麻烦,但是每个应用单独证书需要复制和重启,再配合服务重启一套下来就…...

【Flink】Flink架构及组件
我们学习大数据知识的时候,需要知道大数据组件如何安装以及架构组件,这将帮助我们更好的了解大数据组件 对于大数据Flink,架构图图下: 整个架构图有三种关键组件 1、Client:负责作业的提交。调用程序的 main 方法&am…...

React Navigation 开发准备
需要 React Native 使用 React Navigation 的话,我们需要首先安装如下几个包: npm install react-navigation/native npm install react-native-screens react-native-safe-area-context开发之前做一些处理 如果您使用的是 Mac 并针对 iOS 进行开发&am…...

前端面试:【前端安全】安全性问题与防范措施
嗨,亲爱的前端开发者!在构建Web应用程序时,确保安全性是至关重要的。本文将深入讨论前端开发中的安全性问题,并提供一些防范措施,以确保你的应用程序和用户数据的安全性。 前端安全性问题: 跨站脚本攻击&am…...

[Linux]进程
文章目录 1. 进程控制1.1 进程概述1.1.1 并行和并发1.1.2 PCB1.1.4 进程状态1.1.5 进程命令 1.2 进程创建1.2.1 函数1.2.2 fork() 剖析 1.3 父子进程1.3.1 进程执行位置1.3.2 循环创建子进程1.3.3 终端显示问题1.3.4 进程数数 1.4 execl和execlp函数1.4.1 execl()1.4.2 execlp(…...
01-jupyter notebook的使用方法
一、Tab补全 在shell中输入表达式,按下Tab,会搜索已输入变量(对象、函数等等)的命名空间: 除了补全命名、对象和模块属性,Tab还可以补全其它的。当输入看似文件路径时 (即使是Python字符串&…...

pytestx容器化执行引擎
系统架构 前端、后端、pytest均以Docker容器运行服务,单独的容器化执行引擎,项目环境隔离,即用即取,用完即齐,简单,高效。 前端容器:页面交互,请求后端,展示HTML报告 后…...

(动态规划) 剑指 Offer 42. 连续子数组的最大和 ——【Leetcode每日一题】
❓ 剑指 Offer 42. 连续子数组的最大和 难度:简单 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 要求时间复杂度为 O(n)。 示例1: 输入: nums [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1…...

OLED透明屏曲面技术:创新突破引领显示行业未来
OLED透明屏曲面技术作为一项重要的显示技术创新,正在成为显示行业的焦点,其引人注目的优势和广泛应用领域使其备受关注。 本文将详细介绍OLED透明屏曲面技术的优势、应用领域以及市场前景,同时展望其未来的发展趋势,以期带给读者…...

视频云存储/安防监控EasyCVR视频汇聚平台分发rtsp流时,出现“用户已过期”提示该如何解决?
视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同,支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。音视频流媒体视频平台EasyCVR拓展性强,视频能力丰富,具体可实现视频监控直播、视频轮播、视频录像、…...

调用paddleocr接口实现文本检测与识别,并在图像中显示识别结果
目录 一、按照官网步骤安装paddlepaddle和paddleocr(paddlepaddle我安装的是cpu版本) 二、运行下面的脚本 三、图像结果 一、按照官网步骤安装paddlepaddle和paddleocr(paddlepaddle我安装的是cpu版本) doc/doc_ch/quickstart.md PaddlePaddle/PaddleOCR - Gitee.com 二、…...

如何提升winform程序性能
提升WinForms程序性能是一个关键的优化任务,以下是一些可以帮助你提升性能的方法: 1. **UI延迟加载:** 如果你的WinForms界面很复杂,可以考虑将不必要的UI元素延迟加载,只在需要时加载,以减少启动时间和内…...

按钮权限控制
搜索关键字: 自定义指令传参| "自定义指令""dataset"|自定义指令dataset| "Vue""directives"|vue按钮权限实现 1、完整代码: <template> <div> <el-breadcrumb separator-class"el-icon…...

【脚本式设置环境变量】
在linux系统中,如果我打开一个软件需要如下操作,那将会是一件很麻烦的事情 cd dir #软件的文件路径 conda deactivate conda activate chatgpt python main.py【首先写一个chatgpt.sh脚本内容如下】 #!/bin/bash cd dir conda run -n chatgpt python m…...

软件开发bug问题跟踪与管理
一、Redmine 项目管理和缺陷跟踪工具 官网:https://www.redmine.org/ Redmine 是一个开源的、基于 Web 的项目管理和缺陷跟踪工具。它用日历和甘特图辅助项目及进度可视化显示,同时它又支持多项目管理。Redmine 是一个自由开源软件解决方案,…...

springboot+mp完成简单案例
目录 1.框架搭建 2.前端搭建 3.后端编写 需求:完成简单的连表条件查询以及添加即可 1.框架搭建 1.创建springboot项目 2.相关依赖 <!--web依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boo…...

cuml机器学习GPU库 sklearn升级版AutoDL使用
CUML库 最近在做机器学习任务的时候发现我自己的数据集太大,直接用sklearn 跑起来时间很长,然后问GPT得知了有CUML库,后来去研究了一下,发现这个库只支持linux系统,从官网直接获取下载命令基本上也实现不了最后&#…...

C语言练习题Day1
从今天开始分享C语言的练习题,每天都分享,差不多持续16天,看完对C语言的理解可能更进一步,让我们开始今天的分享吧! 题目一 执行下面的代码,输出结果是() int x5,y7; void swap()…...

使用kubeadm安装和设置Kubernetes(k8s)
用kubeadm方式搭建K8S集群 kubeadm是官方社区推出的一个用于快速部署kubernetes集群的工具。 这个工具能通过两条指令完成一个kubernetes集群的部署: # 创建一个 Master 节点 kubeadm init# 将一个 Node 节点加入到当前集群中 kubeadm join <Master节点的IP和端口…...

Docker安装延迟队列插件
下载插件地址:https://www.rabbitmq.com/community-plugins.html 插件上传服务器 选择跟我们rabbitmq版本一致或者小于的插件即可。版本可在web管理首页查看。 将下载的插件上传到Linux系统上,使用 docker 命令将插件复制到容器内部 plugins目录下 do…...