【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包

这篇具有很好参考价值的文章主要介绍了【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包

前言

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)
where
    F: 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)
where
    F: 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) 之一,意味着抽象并不会引入运行时开销。

总结

以上就是今天要讲的内容文章来源地址https://www.toymoban.com/news/detail-676406.html

到了这里,关于【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月04日
    浏览(52)
  • 【跟小嘉学 Rust 编程】一、Rust 编程基础

    【跟小嘉学 Rust 编程】一、Rust 编程基础 本系列旨在分享 Rust 学习心得,适合初学者入门,后续系列会有 Rust 项目实战系列编程介绍。 主要教材参考 《The Rust Programming Language》 Rust 是一门新的编程语言,它可以让每个人编写可靠且高效的程序,使用于需要运行时速度、需要内

    2024年02月10日
    浏览(45)
  • Rust编程语言入门之Rust的面向对象编程特性

    Rust 受到多种编程范式的影响,包括面向对象 面向对象通常包含以下特性:命名对象、封装、继承 “设计模式四人帮”在《设计模型》中给面向对象的定义: 面向对象的程序由对象组成 对象包装了数据和操作这些数据的过程,这些过程通常被称作方法或操作 基于此定义:

    2023年04月21日
    浏览(54)
  • Rust编程语言入门之高级特性

    不安全 Rust 高级 Trait 高级 类型 高级函数和闭包 宏 隐藏着第二个语言,它没有强制内存安全保证:Unsafe Rust(不安全的 Rust) 和普通的 Rust 一样,但提供了额外的“超能力” Unsafe Rust 存在的原因: 静态分析是保守的。 使用 Unsafe Rust:我知道自己在做什么,并承担相应风险

    2023年04月24日
    浏览(50)
  • 【跟小嘉学 Rust 编程】二十一、网络编程

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(46)
  • 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 本章节涵盖几乎所有编程语言会出现的概念以及他们在 Rust之中的工作原理,这不是 Rust 独有的,但我们将在 Rust 上下文中讨论他们,并且

    2024年02月10日
    浏览(44)
  • 【跟小嘉学 Rust 编程】二十、进阶扩展

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月10日
    浏览(44)
  • 【跟小嘉学 Rust 编程】十五、智能指针

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月11日
    浏览(57)
  • 【跟小嘉学 Rust 编程】三十、Rust 使用 Slint UI

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月04日
    浏览(42)
  • 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 本章节将讲解 Rust 独有的概念(所有权)。所有权是 Rust 最独特的特性,它使得 Rust 能够

    2024年02月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包