0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在Rust中实现Iterator和IntoIterator特征

jf_wN0SrCdH 来源:coding到灯火阑珊 2023-07-02 10:01 次阅读

迭代器是一种强大的工具,它允许对数据结构进行有效的迭代,并且在许多编程语言中都实现了迭代器。然而,Rust独特的所有权系统在如何实现和使用迭代器方面产生了有趣的差异。在本文中,我们将通过创建和实现最常用的迭代器特征——iterator和IntoIterator,来探索这些差异。

假设你有一个这样的结构体:

pubstructTodos{
publist:Vec,
}

pubstructTodo{
pubmessage:String,
pubdone:bool,
}
如果我们希望遍历这个Vec中的每个Todo,我们可以简单地使用它的list属性并遍历它的元素,但是,如果我们想迭代Todos本身,而不暴露其内部属性,该怎么办呢?

Iterator

在Rust中,与Python等语言类似,迭代器是惰性的。这意味着除非对它们进行迭代(也就是消耗它们),否则它们是无效的。

letnumbers=vec![1,2,3];
letnumbers_iter=numbers.iter();
上面的代码创建了一个迭代器——但没有对它做任何操作。要使用迭代器,我们应该创建一个for循环,如下所示:
letnumbers=vec![1,2,3];
letnumbers_iter=numbers.iter();

fornumberinnumbers{
println!("{}",number)
}

还有其他方法可以用来创建迭代器。例如,每次在Rust中使用map()时,我们都在创建一个迭代器。

迭代器 vs 可迭代对象

如前所述,vector是一个可迭代对象。这意味着我们可以对它们进行迭代;但更准确地说,这意味着我们可以使用Vec来创建Iterator。例如,Vec可以生成一个迭代器,但它本身不是迭代器。

创建迭代器

让我们回到Todos结构体看看我们如何创建一种方法来迭代它的元素。Todos有一个字段列表,它是一个Vec

在Rust中,Iterator是一个trait,其中包含一些方法,例如next(),它负责获取集合的下一个元素并返回它,或者如果我们已经到达集合的末尾则返回None。它的实现大致如下:

traitIterator{
typeItem;

fnnext(&mutself)->Option;
}
然后,我们可以创建一种方法来遍历Todos列表字段的元素,编写一个自定义的next()函数。这样做的逻辑很简单——我们可以在伪代码中这样做:
functionnext(){
ifindex< list.len() {
        let todo = Some(list[index])
        self.index += 1
        return todo 
    } else {
        return None
    }
}

在上面的逻辑中,我们检查元素的索引并返回它,前提是它的索引小于列表长度。但我们有个问题。在哪里存储索引?

存储状态

这就是迭代器和可迭代对象之间的区别。翻译上面的伪代码,可以像这样在Todos中实现next()函数,从Iterator trait实现一个方法:

impl<'a>IteratorforTodos{
typeItem=&'aTodo;
fnnext(&mutself)->Option{
ifself.index< self.list.len() {
            let result = Some(&self.list[self.index]);
            self.index += 1;
            result
        } else {
            None
        }
    }
}
但这需要我们改变Todos的结构:我们需要在其中添加一个索引(index)字段,以便每次调用next()时使用——也就是说,每次通过迭代器迭代它的元素时使用。
pubstructTodos{
publist:Vec,
index:usize,
}
在这个逻辑中,我们将修改构造函数,以便始终创建索引为0的结构体。
pubfnnew(list:Vec)->Self{
Todos{list,index:0}
}

然而,这种方法感觉不太对……

在结构体中存储索引字段并不理想,索引用于在迭代器中存储当前状态,因此它应该是迭代器的一部分,而不是结构体的一部分。这就是为什么我们要创建一个迭代器类型——它将存储索引属性——并为该类型实现iterator特性的原因。

TodoIterator

首先,我们需要创建一个迭代器类型:

structTodosIterator<'a>{
todos:&'aTodos,
index:usize,
}

注意这里的生命周期注释,TodosIterator有一个todos字段,它引用了一个Todos。当我们处理引用时,我们需要确保这个字段指向有效的东西——这里就需要生命周期参数

TodosIterator结构体在此的生命周期是'a,基本上,我们使用这个符号来指定迭代器的todos字段需要具有相同的生命周期。这样,我们就可以确保它不能引用已被删除的Todos结构体。

接下来我们来实现TodosIterator的迭代器:

impl<'a>IteratorforTodosIterator<'a>{
typeItem=&'aTodo;

fnnext(&mutself)->Option{
ifself.index< self.todos.list.len() {
            let result = Some(&self.todos.list[self.index]);
            self.index += 1;
            result
        } else {
            None
        }
    }
}
现在我们已经创建了TodoIterator结构体,并为该结构体实现了Iterator特性,我们如何使用它来迭代Todos呢?答案在于Todos结构体的iter()方法,该方法接受Todos的一个引用,并使用它来创建一个迭代器,我们可以使用它来进行迭代!
implTodos{
pubfniter(&self)->TodosIterator{
TodosIterator{
todos:self,
index:0,
}
}
}

//Nowwecaniterate:
fortodointodos.iter(){
println!("{}",todo);//eachtodoisa&Todo,andisimmutable
}

IntoIterator

IntoIterator与Iterator特性有点不同,它有一个单一方法into_iter()返回覆盖数据的迭代器。这使得所有实现IntoIterator的类型都可以转换为Iterator。

让我们来理解它的实现:

pubtraitIntoIterator{
typeItem;
typeIntoIter:Iterator;

fninto_iter(self)->Self::IntoIter;
}

这里有一些关键的点:

1,Item类型参数是迭代器将生成的元素的类型。

2,IntoIter类型参数是into_iter方法返回的迭代器的类型。这个类型必须实现Iterator trait,并且它的Item的类型必须与IntoIterator的Item的类型相同。

3,into_iter方法接受self作为参数,这意味着它使用原始对象并返回一个遍历其元素的迭代器。

怎么实现呢?你可能认为我们可以重用TodosIterator——然而,我们不能这样做,因为它需要&Todos,而且这里需要获得迭代对象的所有权。那么让我们创建另一个迭代器来完成它:

pubstructTodosIntoIterator{
todos:Todos
}

TodosIntoIterator和TodosIterator的区别在于,这里我们没有使用引用,而是获取所有权并返回每个元素本身——这就是为什么我们不再需要生命周期注释了。而且也没有索引来保存状态,我们很快就会看到原因。

遵循IntoIterator trait的定义,我们可以为Todos实现它:

implIntoIteratorforTodos{
typeItem=Todo;
typeIntoIter=TodosIntoIterator;

fninto_iter(self)->TodosIntoIterator{
TodosIntoIterator{todos:self}
}
}
然而,在此之前,我们需要实现TodosIntoIterator的Iterator(还记得类型参数吗?)来描述我们将如何迭代它。
implIteratorforTodosIntoIterator{
typeItem=Todo;

fnnext(&mutself)->Option{
ifself.todos.list.len()==0{
returnNone;
}
letresult=self.todos.list.remove(0);
Some(result)
}
}

这个实现与我们为TodosIterator所做的略有不同,我们利用了Rust中存在的用于Vecs的remove()方法。该方法移除位置n处的元素并将其返回给我们,并给出其所有权(这对于返回Todo而不是&Todo是必要的)。由于这个方法的工作方式,我们总是可以使用“0”来返回第一个元素,而不是存储一个状态并按顺序增加它。

现在,我们完成了!这两种实现使我们能够以两种不同的方式迭代Todos:

1,引用的方式(&Todos)

fortodointodo_list.iter(){
println!("{}",todo);//todoisa&Todo
}
2,获取所有权的方式
fortodointodo_list{
println!("{}",todo);//todoisaTodo
}

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编程语言
    +关注

    关注

    10

    文章

    1928

    浏览量

    34536
  • 迭代器
    +关注

    关注

    0

    文章

    43

    浏览量

    4296
  • Rust
    +关注

    关注

    1

    文章

    228

    浏览量

    6541

原文标题:在Rust中实现Iterator和IntoIterator特征

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    如何使用Rust语言和paho-mqtt模块实现MQTT协议

    模块实现MQTT协议,并重点介绍LWT特征Rust是一种系统级编程语言,它的主要特点是安全、高效、并发。Rust编译器会在编译时进行内存安全检查,避免了很多常见的内存安全问题,如空
    的头像 发表于 09-19 14:41 1845次阅读

    基于Rust语言Hash特征的基础用法和进阶用法

    Rust语言是一种系统级编程语言,具有高性能、安全、并发等特点,是近年来备受关注的新兴编程语言。Rust语言中,Hash是一种常用的数据结构,用于存储键值对。Rust语言提供了一系列
    的头像 发表于 09-19 16:02 1340次阅读

    如何在Rust读写文件

    见的内存安全问题和数据竞争问题。 Rust,读写文件是一项非常常见的任务。本教程将介绍如何在Rust读写文件,包括基础用法和进阶用法。
    的头像 发表于 09-20 10:57 1918次阅读

    Rust 语言中的 RwLock内部实现原理

    中的 RwLock 的内部实现原理、常用接口的使用技巧和最佳实践。 RwLock 的内部实现原理 基本概念 RwLock 是一种读写分离的锁,允许多个线程同时读取共享数据,但只允许一个线程写入数据。通过这种方式,可以避免读写操作之间的竞争,从而提高并发性能。
    的头像 发表于 09-20 11:23 807次阅读

    踩坑rust的partial copy导致metrics丢失

    metrics 丢失。实现,我们设计了一个 rust 的 struct MonitoredStateStoreIterStats 去收集 LSM
    的头像 发表于 01-03 10:02 390次阅读

    只会用Python?教你树莓派上开始使用Rust

    如果您对编程感兴趣,那么您可能听说过Rust。该语言由Mozilla设计,受到开发人员的广泛喜爱,并继续奉献者成长。Raspberry Pi是小型计算机的瑞士军刀,非常适合学习代码。我们将两者
    发表于 05-20 08:00

    怎样去使用Rust进行嵌入式编程呢

    使用Rust进行嵌入式编程Use Rust for embedded development篇首语:Rust的高性能、可靠性和生产力使其适合于嵌入式系统。在过去的几年里,Rust
    发表于 12-22 07:20

    RUST嵌入式开发的应用是什么

    Rust是一种编程语言,它使用户能够构建可靠、高效的软件,尤其是用于嵌入式开发的软件。它的特点是:高性能:Rust具有惊人的速度和高内存利用率。可靠性:在编译过程可以消除内存错误。生产效率:优秀
    发表于 12-24 08:34

    Rust代码中加载静态库时,出现错误 ` rust-lld: error: undefined symbol: malloc `怎么解决?

    我正在 MCUXpresso IDE 创建一个静态库。我正在使用 redlib 我的代码中导入 ` [i]stdlib.h`。它成功地构建了一个静态库。但是,静态库未定义一些标准库函数,例如
    发表于 06-09 08:44

    Java的iterator和foreach遍历集合源代码

    Java的iterator和foreach遍历集合源代码
    发表于 03-17 09:16 9次下载
    Java的<b class='flag-5'>iterator</b>和foreach遍历集合源代码

    Linux内核整合对 Rust 的支持

    Linux Plumbers Conference 2022 大会上举行了一个 Rust 相关的小型会议,该会议讨论的大方向大致为:正在进行的使 Rust 成为一种合适的系统编程语言的工作,以及主线 Linux 内核
    的头像 发表于 09-19 11:06 1146次阅读

    Rust虚幻引擎5的使用

    前段时间,研究了一套 Rust 接入 Maya Plugin 的玩法,主要原理还是使用 C ABI 去交互。那我想着 UE 是使用 C++ 写的,肯定也可以使用 C ABI 去交互,如果可以的话 UE 中就可以使用 Rust
    的头像 发表于 12-21 11:05 5860次阅读

    Rust的错误处理方法

    Rust 没有提供类似于 Java、C++ 的 Exception 机制,而是使用 Result 枚举的方式来实现
    的头像 发表于 02-20 09:37 895次阅读

    rust语言基础学习: rust的错误处理

    错误是软件不可避免的,所以 Rust 有一些处理出错情况的特性。许多情况下,Rust 要求你承认错误的可能性,并在你的代码编译前采取一些行动。
    的头像 发表于 05-22 16:28 2001次阅读

    RustPin/Unpin详解

    对我来说,其中之一就是Rust Pin/Unpin 。
    的头像 发表于 07-20 11:00 816次阅读