迭代器是一种强大的工具,它允许对数据结构进行有效的迭代,并且在许多编程语言中都实现了迭代器。然而,rust独特的所有权系统在如何实现和使用迭代器方面产生了有趣的差异。在本文中,我们将通过创建和实现最常用的迭代器特征——iterator和intoiterator,来探索这些差异。
假设你有一个这样的结构体:
pub struct todos { pub list: vec,}pub struct todo { pub message: string, pub done: bool,}如果我们希望遍历这个vec中的每个todo,我们可以简单地使用它的list属性并遍历它的元素,但是,如果我们想迭代todos本身,而不暴露其内部属性,该怎么办呢?
iterator
在rust中,与python等语言类似,迭代器是惰性的。这意味着除非对它们进行迭代(也就是消耗它们),否则它们是无效的。
let numbers = vec![1, 2, 3];let numbers_iter = numbers.iter();上面的代码创建了一个迭代器——但没有对它做任何操作。要使用迭代器,我们应该创建一个for循环,如下所示:let numbers = vec![1, 2, 3];let numbers_iter = numbers.iter();for number in numbers { println!({}, number)}
还有其他方法可以用来创建迭代器。例如,每次在rust中使用map()时,我们都在创建一个迭代器。
迭代器 vs 可迭代对象
如前所述,vector是一个可迭代对象。这意味着我们可以对它们进行迭代;但更准确地说,这意味着我们可以使用vec来创建iterator。例如,vec可以生成一个迭代器,但它本身不是迭代器。
创建迭代器
让我们回到todos结构体看看我们如何创建一种方法来迭代它的元素。todos有一个字段列表,它是一个vec。
在rust中,iterator是一个trait,其中包含一些方法,例如next(),它负责获取集合的下一个元素并返回它,或者如果我们已经到达集合的末尾则返回none。它的实现大致如下:
trait iterator { type item; fn next(&mut self) -> option;}然后,我们可以创建一种方法来遍历todos列表字段的元素,编写一个自定义的next()函数。这样做的逻辑很简单——我们可以在伪代码中这样做:function next() { if index option { if self.index self { todos { list, index: 0 }}
然而,这种方法感觉不太对……
在结构体中存储索引字段并不理想,索引用于在迭代器中存储当前状态,因此它应该是迭代器的一部分,而不是结构体的一部分。这就是为什么我们要创建一个迭代器类型——它将存储索引属性——并为该类型实现iterator特性的原因。
todoiterator
首先,我们需要创建一个迭代器类型:
struct todositerator { todos: &'a todos, index: usize,}
注意这里的生命周期注释,todositerator有一个todos字段,它引用了一个todos。当我们处理引用时,我们需要确保这个字段指向有效的东西——这里就需要生命周期参数。
todositerator结构体在此的生命周期是'a,基本上,我们使用这个符号来指定迭代器的todos字段需要具有相同的生命周期。这样,我们就可以确保它不能引用已被删除的todos结构体。
接下来我们来实现todositerator的迭代器:
impl iterator for todositerator { type item = &'a todo; fn next(&mut self) -> option { if self.index todositerator { todositerator { todos: self, index: 0, } }}// now we can iterate:for todo in todos.iter() { println!({}, todo); // each todo is a &todo, and is immutable}
intoiterator
intoiterator与iterator特性有点不同,它有一个单一方法into_iter()返回覆盖数据的迭代器。这使得所有实现intoiterator的类型都可以转换为iterator。
让我们来理解它的实现:
pub trait intoiterator { type item; type intoiter: iterator; fn into_iter(self) -> self::intoiter;}
这里有一些关键的点:
1,item类型参数是迭代器将生成的元素的类型。
2,intoiter类型参数是into_iter方法返回的迭代器的类型。这个类型必须实现iterator trait,并且它的item的类型必须与intoiterator的item的类型相同。
3,into_iter方法接受self作为参数,这意味着它使用原始对象并返回一个遍历其元素的迭代器。
怎么实现呢?你可能认为我们可以重用todositerator——然而,我们不能这样做,因为它需要&todos,而且这里需要获得迭代对象的所有权。那么让我们创建另一个迭代器来完成它:
pub struct todosintoiterator { todos: todos}
todosintoiterator和todositerator的区别在于,这里我们没有使用引用,而是获取所有权并返回每个元素本身——这就是为什么我们不再需要生命周期注释了。而且也没有索引来保存状态,我们很快就会看到原因。
遵循intoiterator trait的定义,我们可以为todos实现它:
impl intoiterator for todos { type item = todo; type intoiter = todosintoiterator; fn into_iter(self) -> todosintoiterator { todosintoiterator { todos: self } }}然而,在此之前,我们需要实现todosintoiterator的iterator(还记得类型参数吗?)来描述我们将如何迭代它。impl iterator for todosintoiterator { type item = todo; fn next(&mut self) -> option { if self.todos.list.len() == 0 { return none; } let result = self.todos.list.remove(0); some(result) }}
这个实现与我们为todositerator所做的略有不同,我们利用了rust中存在的用于vecs的remove()方法。该方法移除位置n处的元素并将其返回给我们,并给出其所有权(这对于返回todo而不是&todo是必要的)。由于这个方法的工作方式,我们总是可以使用“0”来返回第一个元素,而不是存储一个状态并按顺序增加它。
现在,我们完成了!这两种实现使我们能够以两种不同的方式迭代todos:
1,引用的方式(&todos)
for todo in todo_list.iter() { println!({}, todo);// todo is a &todo}2,获取所有权的方式for todo in todo_list { println!({}, todo); // todo is a todo}
电容滤波在失效整改中使用什么元器件进行滤波?
如何确定上拉的大小?I2C总线如何实现双向通信
专访17173总经理赵佳:游戏TOC媒体的两条生路和三个机会
博泽集团电子控制单元的概念车门,助于车厂降低研发成本和节省空间
黑莓不玩手机之后的卷土重来:无人驾驶领域
在Rust中实现Iterator和IntoIterator特征
湖南建设一批智能制造示范项目,打造闻名全国的智能制造“湖南特色”
无阻值电阻在电路中扮演什么角色?巨磁电阻的构成和一般电阻有什么不同吗?
工业机器人应用的十大误区(二)
【分享】什么是Z-Wave?为何它没有ZigBee普及?
TIA博途V13移植PLC的基本步骤
中兴Axon 30S搭载完整无缺全面屏
1206封装 510种阻值 每种阻值500片 贴片电阻盒
协同创新,做强中国动力电池产业
数控机床plc编程详解
基于被动红外 (PIR) 的运动探测器
OPPO 成立安第斯事业部负责云平台、数据中心以及基础技术
电瓶修复技术—充电与硫化是否密不可分
选云,EDA厂商上云的第一步
你会操作在嵌入式Linux下PWM功能调试?