Rust开发⼲货集(1)--迭代器与消费器

alt

本内容是对 Rust开发干货集[1] 的实践与扩展.


iter() 不转移所有权


先简单解释下什么叫"转移所有权":

在 Rust 中,"转移所有权"(Ownership Transfer)是一种核心概念,它涉及变量和数据的所有权从一个实体转移到另一个实体。这种机制帮助 Rust 在编译时期管理内存安全,避免悬挂指针和内存泄漏等问题。

例如:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // 所有权从 s1 转移到 s2

    // println!("{}", s1); // 这行会引发编译错误,因为 s1 不再拥有数据的所有权. 报错: error[E0382]: borrow of moved value: `s1`
    println!("{}", s2); // 正确,s2 现在拥有数据的所有权
}

当 s1 被赋值给 s2 时,s1 的所有权被转移给了 s2。这意味着 s1 不再有效,因此接下来如果使用 s1 将导致编译错误。


iter() 在 Rust 中用于创建集合的迭代器,比如在数组或向量上。iter() 不会转移集合的所有权。相反,它创建一个迭代器,该迭代器借用集合的内容:

fn main() {
    let v = vec![123];

    for i in v.iter() {
        println!("{}", i);
    }

    // v 仍然有效,因为 iter() 没有取得所有权
    println!("Vector: {:?}", v);
}

上例中,v.iter() 创建了一个迭代器,但 v 的所有权没有改变。因此,在迭代之后,仍然可以使用 v

这说明iter() 不转移所有权(因为所有权转移意味着原始变量不再有效)


另外几种创建迭代器的方法: iter_mut()into_iter()


iter_mut()

iter_mut() 方法用于创建一个可变借用(mutable borrow)的迭代器。其允许在迭代过程中修改集合中的元素。

(所有权并没有发生转移)

如下:

fn main() {
    let mut v = vec![123];

    for i in v.iter_mut() {
        *i *= 2// 将每个元素乘以 2
        println!("{}", i);
    }

    println!("{:?}", v); // 输出: [2, 4, 6]
}

v.iter_mut() 创建了一个可变迭代器,允许修改向量 v 中的每个元素


into_iter()

into_iter() 方法用于创建一个取得所有权(ownership)的迭代器---这意味着迭代器会消耗(consume)集合,并拥有其元素的所有权。这通常用于在迭代时转移集合中元素的所有权。


如下:

fn main() {
    let v = vec![123];

    for i in v.into_iter() {
        println!("{}", i); // 打印每个元素
    }

    // println!("{:?}", v); // 这行会编译错误,因为 v 的所有权已经被移动; error[E0382]: borrow of moved value: `v`
}

v.into_iter() 创建了一个获取 v 所有权的迭代器。迭代后,v 不再有效,因为它的所有权已经被迭代器 into_iter() 消耗。

into_iter() 会转移所有权。它创建一个获取集合所有权的迭代器,允许在迭代时转移集合中元素的所有权。一旦使用了 into_iter(),原始集合将不再有效,因为它的所有权已经被迭代器取得。


iter_mut() 用于需要修改集合中元素的场景,但并不转移所有权; 而 into_iter() 用于需要转移元素所有权的场景。


iter()cloned()方法


iter() 方法用于创建一个不可变引用的迭代器,而 cloned() 是这类迭代器的一个方法。cloned() 的作用是将迭代器中的每个元素通过调用其 clone 方法来创建一个新的实例。这通常用于当拥有一个包含引用的迭代器,但需要迭代器中的值的拷贝时。


cloned() 的作用:

  • 创建元素的拷贝cloned() 方法适用于迭代器的元素实现了 Clone trait。它会为每个元素创建一个新的实例,这是通过调用每个元素的 clone 方法实现的。

  • 不转移所有权:由于 cloned() 仅仅是创建元素的副本,它不会改变原始数据的所有权。

如下例: 假设有一个Vec,其中包含一些数字的引用,现在想要迭代这些数字的拷贝而不是引用本身:

use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("变量类型为:{}", type_name::<T>());
}

fn main() {
    let nums = vec![123];

    for num in nums.iter() {
        println!("{}", num); // 输出: 1, 2, 3
        print_type_of(&num);
    }

    println!("-----分界线------");
    let nums_cloned: Vec<i32> = nums.iter().cloned().collect();

    println!("{:?}", nums_cloned); // 输出: [1, 2, 3]

    for num in nums_cloned {
        println!("{}", num); // 输出: 1, 2, 3
        print_type_of(&num);
    }
}

输出为:

1
变量类型为:&i32
2
变量类型为:&i32
3
变量类型为:&i32
-----分界线------
[123]
1
变量类型为:i32
2
变量类型为:i32
3
变量类型为:i32

在上例中,使用 cloned() 方法,可以将这些引用转换为实际的数字拷贝,从而创建一个新的 Vec<i32>。这样,nums_cloned 就包含了 nums 中每个元素的拷贝,而不是引用。

cloned() 在 Rust 中用于从迭代器中创建元素的拷贝,特别是当有一个包含引用的迭代器 并希望获得实际值的拷贝时。它是处理引用集合时常用的便捷方法。


iter_mut() 有没有 cloned()方法?


iter_mut() 方法返回的迭代器是一个可变引用的迭代器。由于 cloned() 方法是用于拷贝迭代器中的值,它通常与不可变引用的迭代器(如由 iter() 返回的迭代器)一起使用。cloned() 方法适用于那些实现了 Clone trait 的元素,它会创建每个元素的拷贝。

对于 iter_mut() 返回的迭代器,由于它提供的是对元素的可变引用(&mut T),使用 cloned() 方法是不适当的,也不符合 Rust 的安全性原则。可变引用的目的是允许修改集合中的元素,而不是创建它们的拷贝。如果需要修改元素并且需要它们的拷贝,应该首先通过其他方式创建拷贝,然后对这些拷贝进行修改。

因此,在实际的 Rust 编程实践中,iter_mut() 迭代器上不会使用 cloned() 方法。如果需要元素的拷贝,应该使用 iter() 方法来创建一个不可变引用的迭代器,然后在该迭代器上使用 cloned()


map/fold(reduce)/filter的作用


更多可参考 初探函数式编程---以Map/Reduce/Filter为例[2]


map用于对迭代器中的 每个元素 应用某个函数/执行某项(会发生修改的)操作,并返回一个新的迭代器。

例如:

let numbers = [12345];

let doubles = numbers.iter().map(|x| x * 2);
// doubles将包含[2, 4, 6, 8, 10]
fn main() {
    let numbers = [12345];

    let doubles = numbers.iter().map(|x| x * 2);

    for x in doubles {
        println!("{}", x);
    }
}

输出:

2
4
6
8
10


fold用于将迭代器中的元素进行累积计算,其基本语法是:

iter.fold(init, |acc, x| {
   // acc是累积值,x是当前元素
   // 返回更新后的acc
})

fold需要两个参数:

  • init:初始累积值
  • 闭包:接收当前累积值acc和元素x,返回更新后的acc

例如:

fn main() {
    let numbers = [12345];

    let sum = numbers.iter().fold(0, |acc, x| acc + x);

    println!("Sum is: {}", sum); // Sum is: 15
}

这里fold的init值为0,闭包中每次将acc和x相加,返回更新后的acc,最终将数组所有元素求和。


另一个例子,连接字符串:

fn main() {
    let strings = ["a""b""c"];

    let concat = strings.iter().fold(String::new(), |acc, s| acc + s);

    println!("Concat is: {}", concat); // Concat is: abc
}

fold可以将迭代中的元素进行任意累积计算,常见用法包括求和、乘积、字符串连接等。fold()消费器可以实现reduce逻辑



filter用于过滤迭代器中的元素,只保留满足条件的元素。

例如:

let numbers = [12345];

let evens = numbers.iter().filter(|&x| x % 2 == 0);
// evens将只包含[2, 4]
fn main() {
    let numbers = [12345];

    let evens: Vec<_> = numbers.iter().filter(|&x| x % 2 == 0).collect();

    for x in evens {
        println!("{}", x);
    }
}

输出:

2
4

filter_map() 可以同时完成转换和过滤


filter_map() 方法结合了过滤(filter)和映射(map)功能。

这个方法接收一个闭包,该闭包作用于迭代器的每个元素,并返回 Option<T> 类型。filter_map() 然后自动过滤掉所有返回 None 的元素,并将所有 Some 包裹的值提取出来,形成一个新的迭代器。

  • 过滤和转换filter_map() 允许同时对迭代器的元素进行过滤和转换。如果闭包返回 Some(value),则 value 被包含在新迭代器中;如果闭包返回 None,则该元素被过滤掉。

  • 可选的转换:与 map() 相比,filter_map() 允许根据元素的值选择性地包含或排除元素,而不是简单地映射每个元素到另一个值。


举个例子, 假设有一个字符串类型的向量,想将其中的每个字符串转换为整数。但不是所有的字符串都可以转换为整数(例如,某些字符串可能包含非数字字符,如"1ab")。

在这种情况下,就可以使用 filter_map() 来尝试解析每个字符串,并仅保留那些成功解析为整数的元素:

fn main() {
    let strings = vec!["3""seven""8""10"];

    let numbers: Vec<i32> = strings
        .into_iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();

    println!("{:?}", numbers); // 输出: [3, 8, 10]
}

在上例中,filter_map() 尝试将每个字符串转换为 i32。如果 parse 方法成功(即返回 Ok(value)),filter_map()Some(value) 包装的 value 加入到新的迭代器中。如果 parse 失败(即返回 Err),filter_map() 则通过 ok() 方法将 Err 转换为 None,从而过滤掉这些元素。

通过这种方式,filter_map() 使得同时进行过滤和映射变得简单而高效。


另外一些消费器


上面介绍的 mapfoldfilter ,都属于消费器, 消费器在Rust中是指能够消费迭代器的类型

另外还有一些常用的消费器,包括:

  • collect():将迭代器收集到集合类型如Vec/String中

collect()消费器可以实现集合类型转化

fn main() {
    println!("{:?}"vec![123].iter().collect::<Vec<_>>()); // [1, 2, 3]
}
  • sum():计算迭代器元素的和。
fn main() {
    // sum()的返回类型依赖于迭代器的元素类型,这里元素是i32,所以需要明确指定sum_value的类型为i32
    let sum_value1: i32 = [123].iter().sum();
    println!("{:?}", sum_value1); // 6

    // 或者使用泛型参数指定
    let sum_value2 = [123].iter().sum::<i32>();
    println!("{:?}", sum_value2); // 6
}
  • min()/max():找到迭代器最小/最大元素。
fn main() {
    let max = [123].iter().max();
    println!("{:?}", max.unwrap()); // 3
}
  • count():统计迭代器元素数量。
fn main() {
    let count = [12306].iter().count();
    println!("{:?}", count); // 5
}
  • any()/all():是否存在(某个元素)/所有元素满足条件。

any()消费器可以查找是否存在满⾜条件的元素,迭代器是惰性的,any消费器可能不需要遍历Iterator

fn main() {
    let exists = [123].iter().any(|x| *x == 2);
    println!("{:?}", exists); // true, 即判断Vec中是否有值为2的元素
}
fn main() {
    let numbers = [246];

    let result = numbers.iter().all(|x| x % 2 == 0);

    println!("{:?}", result); // true, 即判断Vec中所有元素是否都能被2整除
}
  • find():找到第一个满足条件的元素并输出,否则返回None
fn main() {
    let numbers = [1234];

    // 查找numbers中第一个等于3的元素,找到后返回Some(3)
    let result = numbers.iter().find(|&x| *x == 3);
    println!("{:?}", result); // Some(3)

    // 如果没有找到满足条件的元素,则会返回None:
    let result = numbers.iter().find(|&x| *x == 5);
    println!("{:?}", result); // None
}

这些消费器都会完全消费迭代器,将其结果返回。它们常用于迭代器计算的最后阶段,将迭代结果转化为具体值。

参考资料

[1]

Rust开发干货集: https://github.com/rustaceanclub/rust-slides/tree/master/20191216-GB-Rust%E5%BC%80%E5%8F%91%E5%B9%B2%E8%B4%A7%E9%9B%86

[2]

初探函数式编程---以Map/Reduce/Filter为例: https://juejin.cn/post/7270823313809752076

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/280540.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023.12.28 关于 Redis 数据类型 List 内部编码、应用场景

目录 List 编码方式 早期版本 现今版本 List 实际应用 多表之间的关联关系 消息队列 频道&#xff08;多列表&#xff09;消息队列 微博 Timeline 栈 & 队列 List 编码方式 早期版本 早期版本 List 类型的内部编码方式有两种 ziplist&#xff08;压缩列表&#xf…

vscode: make sure you configure your user.name and user.email in git

一、问题描述 使用VScode编辑代码后&#xff0c;Push到云端报错&#xff1a;Make sure you configure your "user.name" and "user.email" in git 二、解决方案 解决步骤&#xff1a; 1.打开Git Bash&#xff1a; 2.输入命令&#xff1a; git config -…

ElasticSearch学习笔记(二)

通过前面的一阵胡乱操作&#xff0c;显然提升了我的学习兴趣&#xff0c;趁热打铁&#xff0c;接着往下学。还是先看看别人的教程吧。这里我看的是B站上【尚硅谷】的ElasticSearch教程&#xff0c;有兴趣的同学也可以去看看。 一、缘起–索引操作 看B站上的视频教程&#xff0…

命令模式-实例使用

未使用命令模式的UML 使用命令模式后的UML public abstract class Command {public abstract void execute(); }public class Invoker {private Command command;/*** 为功能键注入命令* param command*/public void setCommand(Command command) {this.command command;}/***…

数据库原理与应用快速复习(期末急救)

文章目录 第一章数据库系统概述数据、数据库、数据库管理系统、数据定义、数据组织、存储和管理、数据操纵功能、数据库系统的构成数据管理功能、数据库管理的3个阶段以及特点数据库的特点、共享、独立、DBMS数据控制功能数据库的特点 数据模型两类数据模型、逻辑模型主要包括什…

SVN下载安装(服务器与客户端)

1.下载 服务器下载&#xff1a;Download | VisualSVN Server 客户端下载&#xff1a;自行查找 2. 服务器安装 双击执行 运行 下一步 同意下一步 下一步 选中安装目录 3. 客户端安装 双击执行 下一步 4. 服务器创建仓库 5. 服务器创建用户 6. 客户端获取资源 文件夹右键

基于ssm的便民自行车管理系统的开发与实现+vue论文

摘 要 进入21世纪网络和计算机得到了飞速发展&#xff0c;并和生活进行了紧密的结合。目前&#xff0c;网络的运行速度以达到了千兆&#xff0c;覆盖范围更是深入到生活中的角角落落。这就促使管理系统的发展。管理系统可以实现远程处理事务&#xff0c;远程提交工作和随时追踪…

电路分析基础速成课笔记

基础知识 串并联&#xff0c;短路和断路 电源 例题&#xff1a; 电阻电路的等效变换 电压源串联 u u 1 u 2 uu_1u_2 uu1​u2​ 方向相同直接加&#xff0c;不同取决于大的方向 电流源并联 i i s 1 i s 2 ii_{s_1}i_{s_2} iis1​​is2​​ 电压源和电流源串联 省略电压…

通信原理课设(gec6818) 007:语音识别

目录 1、去科大讯飞官网下载对应的sdk 2、科大讯飞文件夹的意思 3、配置ARM的录音环境 4、编程实现语音识别 我们的需求是将一个语音文件从客户端传到服务器&#xff0c;因此我们最好是选用tcp 现在市面上面常用的语音识别解决方案为&#xff1a;科大讯飞c和百度c 离…

python+django高校教材共享管理系统PyCharm 项目

本中原工学院教材共享平台采用的数据库是mysql&#xff0c;使用nodejs技术开发。在设计过程中&#xff0c;充分保证了系统代码的良好可读性、实用性、易扩展性、通用性、便于后期维护、操作方便以及页面简洁等特点。系统所要实现的功能分析&#xff0c;对于现在网络方便的管理&…

[计算机提升] Windows系统软件:管理类

3.6 系统软件&#xff1a;管理类 3.6.1 运行 通过运行程序&#xff0c;在打开输入框中输入名称&#xff0c;按下回车后可以打开相应的程序、文件夹、文档或Internet资源&#xff1a; 3.6.2 命令提示符&#xff1a;cmd 在Windows系统中&#xff0c;cmd是指"命令提示符…

HTML5+CSS3+JS小实例:过年3D烟花秀

实例:过年3D烟花秀 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><…

【web安全】验证码识别-burp的captcha-killer-modified插件教程(基于百度接口)(总结一些坑)

前言 菜某分享 captcha-killer-modified插件的安装教程 整体安装教程可以看他的 安装captcha-killer-modified插件&#xff08;windospython环境&#xff09;_aptcha-killer-modified的安装-CSDN博客 但是有一点补充。 这个里面的codereg.py文件有个问题 可能是版本的问…

AI绘画工具Midjourney绘画提示词Prompt分享

一、Midjourney绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭…

基于NXP I.MX8 + Codesys的工业软PLC解决方案

全新i.MX 8M Plus是一个混合人工智能SoC&#xff0c;将先进的嵌入式SoC与最新的人工智能/机器学习硬件NPU技术相结合&#xff0c;通过神经网络加速器&#xff0c;为边缘计算提供强大的机器学习能力&#xff0c;是i.MX 8M Plus一个最为突出的优势。WEC-IMX8P核心板特别适合在机器…

uni-app API接口扩展组件(uni-ui)

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

如何恢复 iPhone 上永久删除的照片?

2007年&#xff0c;苹果公司推出了一款惊天动地的智能手机&#xff0c;也就是后来的iPhone。你会惊讶地发现&#xff0c;迄今为止&#xff0c;苹果公司已经售出了 7 亿部 iPhone 设备。根据最新一项调查数据&#xff0c;智能手机利润的 95% 都进了苹果公司的腰包。 如此受欢迎…

探讨kernel32.dll文件是什么,有效解决kernel32.dll丢失

在使用电脑时&#xff0c;你是否遇到过kernel32.dll丢失的困扰&#xff1f;面对这个问题&#xff0c;我们需要及时去解决kernel32.dll丢失的问题。接下来&#xff0c;我们将深入探讨kernel32.dll的功能以及其在操作系统和应用程序中的具体应用领域&#xff0c;相信这将对你解决…

统一系统脆弱性管理平台:“新一代”漏洞扫描管理系统

网络安全风险管理是政府企事业单位面临的巨大挑战&#xff0c;尤其是数字化转型带来了更多在线、移动化的新应用场景&#xff0c;随着业务量迅速增长&#xff0c;更为复杂的脆弱性管理难题也变得更加繁琐棘手。 网络安全漏洞可能被有意或无意地利用&#xff0c;从而对政府、企事…

【目标检测】yolov8结构及代码分析

yolov8代码:https://github.com/ultralytics/ultralytics yolov8的整体结构如下图&#xff08;来自mmyolo&#xff09;&#xff1a; yolov8的配置文件&#xff1a; # Ultralytics YOLO &#x1f680;, AGPL-3.0 license # YOLOv8 object detection model with P3-P5 outputs.…
最新文章