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

news2025/1/18 6:58:01
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.coloradmin.cn/o/1345214.html

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

相关文章

C/C++ BM3 链表中的节点每k个一组翻转

文章目录 前言题目思路阐述代码总结 前言 这道题的关键是理解链表指针的位置&#xff1b; 在BM2的区间翻转基础上&#xff0c;多了个指针偏移&#xff0c;博客里面我贴图阐述一下。 题目 思路阐述 这道题的翻转过程参考BM2的题解&#xff0c;这里主要阐述一下指针移动和整体思…

redhat 8 安装openstack

redhat 8 安装openstack 1、安装文档2、redhat 8 安装openstack3、使用openstack 1、安装文档 openstack官方安装文档 https://docs.openstack.org/install-guide/ 2、redhat 8 安装openstack 3、使用openstack

【C/C++笔试练习】sort排序、STL容器、vector的特性、一级容器、迭代器失效、异常捕获、动态转换、统计每个月兔子的总数、字符串通配符

文章目录 C/C笔试练习选择部分&#xff08;1&#xff09;sort是不稳定排序&#xff08;2&#xff09;存放即有序的STL容器&#xff08;3&#xff09;连续储存的STL容器&#xff08;4&#xff09;vector的特性&#xff08;5&#xff09;一级容器&#xff08;6&#xff09;unorde…

2、gdb常用功能2

1.4、线程 程序避免不了涉及到多线程.常用指令如下. 命令简写形式说明info thread显示当前进程内所有线程信息thread 切换到num线程thread find 寻找regexp在gdb中的idinfo address 结合上述图片理解&#xff0c;第一列的id是gdb内部为线程排序的一个id&#xff0c;第三列中…

Redis:原理速成+项目实战——Redis的Java客户端

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;Redis&#xff1a;原理速成项目实战——Redis常见命令&#xff08;数据结构、常见命令总结&#xff09; &#x1f4da;订阅专栏&…

算法基础之最短编辑距离

最短编辑距离 核心思想 &#xff1a; 线性dp 集合定义 &#xff1a; f[i][j]为操作方式的最小值 集合计算 : 三种操作 取最小 ① 删除 : 将a[i]删掉 使ab相同 –> f[i-1][j] 1 f[i][j]② 增添 : 在a[i]后加上一个数 使ab相同 –> f[i][j-1] 1 f[i][j]③ 替换 : 将a[…

【unity学习笔记】捏人+眨眼效果+口型效果

一、vriod捏人 1.在vroidstudio软件中捏人 2.导出模型&#xff08;.vrm) 二、vrid导入unity的插件 1.在Git上搜索、打开univrm。 2.找到release页面找到合适的插件版本。&#xff08;VRM-0.116.0_0f6c&#xff09; 3.将univrm导入到工程中&#xff08;assets&#xff09;。 三…

基于FPGA的数字电路(PWM脉宽调制)

一.PWM的制作原理 假如我们有一个锯齿波&#xff0c;然后在锯齿波上设置一个阈值&#xff08;黑色水平虚线&#xff09;&#xff0c;凡是大于该阈值时输出均为高电平&#xff0c;反之则为低电平&#xff0c;这样我们是不是就得到一个PWM信号呢&#xff1f;如果我们想调整它的占…

数据治理:释放数据价值的关键

随着数字化时代的到来&#xff0c;数据已成为组织和企业最重要的资产之一。然而&#xff0c;数据的快速增长和复杂性也给数据管理带来了巨大的挑战。为了确保数据的质量、安全性和合规性&#xff0c;数据治理已成为组织和企业必须面对的重要问题。数据治理是数据要素市场建设的…

OSG绘制视锥体(升级版)

OSG绘制视锥体&#xff0c;这一篇增加设置相机参数接口&#xff0c;支持通过eye、center、up设置相机参数。 代码如下&#xff1a; #include "stdafx.h" #include <osgViewer/Viewer> #include <osg/ShapeDrawable> #include <osg/Geode> #includ…

SkyWalking UI 修改发布Nginx

文章目录 SkyWalking UI修改图标修改路由发布到Nginx添加认证修改路由模式vite.config.ts添加baseNginx配置 SkyWalking UI skywalking-booster-ui下载地址 修改图标 替换 logo.svg 修改路由 router - data - index.ts 发布到Nginx 添加认证 # 安装 yum install -y h…

电子学会C/C++编程等级考试2022年12月(八级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:生理周期(2022.12) 人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为23天、28天和33天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容…

【网络安全常用术语解读】SCAP详解

本文主要介绍什么是SCAP&#xff0c;SCAP的产生背景是怎样的&#xff0c;SCAP有什么用途&#xff0c;有哪些组件&#xff0c;各个组件的用途是什么&#xff1f; SCAP产生背景 由于计算机和网络技术的快速发展&#xff0c;越来越多的软件和系统被应用到企业和机构中&#xff0c…

安装与部署Hadoop

一、前置依赖1、java 一、前置依赖 1、java 需要安装java rz tar -zxvf jdk-8u381-linux-x64.tar.gz -C / ln -s /jdk1.8.0_381/ /jdk # rm jdk-8u381-linux-x64.tar.gzvim /etc/profile export JAVA_HOME/jdk export PATH$PATH:$JAVA_HOME/bin # source /etc/profile ln -s…

Strateg策略模式(组件协作)

策略模式&#xff08;组件协作&#xff09; 链接&#xff1a;策略模式实例代码 注解 目的 正常情况下&#xff0c;一个类/对象中会包含其所有可能会使用的内外方法&#xff0c;但是一般情况下&#xff0c;这些常使用的类都是由不同的父类继承、组合得来的&#xff0c;来实现…

2023.12.30 Pandas操作

目录 1. pandas基础 1.1 pandas的基本介绍 1.2 pandas基础使用 2. pandas的数据结构 2.1 series对象 2.2 使用列表,自定义索引,字典,元组方式创建series对象 2.3 Series对象常用API 2.4 Series 对象的运算 1. pandas基础 1.1 pandas的基本介绍 Python在数据处理上独步天下…

SQL server Profiler的使用

在SQL server management studio 中 的工具下 选择SQL server Profiler 对sql进行过滤

计算机网络【EPOLL 源码详解】

IO多路复用 在以前&#xff0c;传统的网络编程是多线程模型&#xff0c;一个线程单独处理一个请求。 然而&#xff0c;线程是很昂贵的资源&#xff1a; 线程的创建和销毁成本很高&#xff0c;linux的线程实际上是特殊的进程&#xff1b;因此通常会使用线程池来减少线程创建和…

React中super() 和 super(props) 有什么区别?

面试官&#xff1a;super() 和 super(props) 有什么区别&#xff1f; 一、ES6 类 在 ES6 中&#xff0c;通过 extends 关键字实现类的继承&#xff0c;方式如下&#xff1a; class sup {constructor(name) {this.name name;}printName() {console.log(this.name);} }class s…

Android 13 - Media框架(27)- ACodec(五)

前面几节我们了解了OMXNodeInstance是如何处理setPortMode、allocateBuffer、useBuffer的&#xff0c;这一节我们再回到ACodec&#xff0c;来看看 ACodec start 的其他部分。 我们首先来回顾一下&#xff0c;ACodec start 的状态切换以及处理的事务&#xff0c;我们用一张不太准…