迭代器是一种强大的工具,它允许对数据结构进行有效的迭代,并且在许多编程语言中都实现了迭代器。然而,Rust独特的所有权系统在如何实现和使用迭代器方面产生了有趣的差异。在本文中,我们将通过创建和实现最常用的迭代器特征——iterator和IntoIterator,来探索这些差异。
pub struct Todos {
pub list: Vec<Todo>,
}
pub struct Todo {
pub message: String,
pub done: bool,
}
Iterator
let numbers = vec![1, 2, 3];
let numbers_iter = numbers.iter();
let numbers = vec![1, 2, 3];
let numbers_iter = numbers.iter();
for number in numbers {
println!("{}", number)
}
还有其他方法可以用来创建迭代器。例如,每次在Rust中使用map()时,我们都在创建一个迭代器。
迭代器 vs 可迭代对象
如前所述,vector是一个可迭代对象。这意味着我们可以对它们进行迭代;但更准确地说,这意味着我们可以使用Vec来创建Iterator。例如,Vec<u32>可以生成一个迭代器,但它本身不是迭代器。
创建迭代器
让我们回到Todos结构体看看我们如何创建一种方法来迭代它的元素。Todos有一个字段列表,它是一个Vec<Todo>。
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
function next() {
if index < list.len() {
let todo = Some(list[index])
self.index += 1
return todo
} else {
return None
}
}
在上面的逻辑中,我们检查元素的索引并返回它,前提是它的索引小于列表长度。但我们有个问题。在哪里存储索引?
存储状态
impl<'a> Iterator for Todos {
type Item = &'a Todo;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.list.len() {
let result = Some(&self.list[self.index]);
self.index += 1;
result
} else {
None
}
}
}
pub struct Todos {
pub list: Vec<Todo>,
index: usize,
}
pub fn new(list: Vec<Todo>) -> Self {
Todos { list, index: 0 }
}
然而,这种方法感觉不太对……
在结构体中存储索引字段并不理想,索引用于在迭代器中存储当前状态,因此它应该是迭代器的一部分,而不是结构体的一部分。这就是为什么我们要创建一个迭代器类型——它将存储索引属性——并为该类型实现iterator特性的原因。
TodoIterator
struct TodosIterator<'a> {
todos: &'a Todos,
index: usize,
}
注意这里的生命周期注释,TodosIterator有一个todos字段,它引用了一个Todos。当我们处理引用时,我们需要确保这个字段指向有效的东西——这里就需要生命周期参数。
TodosIterator结构体在此的生命周期是'a,基本上,我们使用这个符号来指定迭代器的todos字段需要具有相同的生命周期。这样,我们就可以确保它不能引用已被删除的Todos结构体。
impl<'a> Iterator for TodosIterator<'a> {
type Item = &'a Todo;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.todos.list.len() {
let result = Some(&self.todos.list[self.index]);
self.index += 1;
result
} else {
None
}
}
}
impl Todos {
pub fn iter(&self) -> 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<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
这里有一些关键的点:
1,Item类型参数是迭代器将生成的元素的类型。
2,IntoIter类型参数是into_iter方法返回的迭代器的类型。这个类型必须实现Iterator trait,并且它的Item的类型必须与IntoIterator的Item的类型相同。
3,into_iter方法接受self作为参数,这意味着它使用原始对象并返回一个遍历其元素的迭代器。
pub struct TodosIntoIterator {
todos: Todos
}
TodosIntoIterator和TodosIterator的区别在于,这里我们没有使用引用,而是获取所有权并返回每个元素本身——这就是为什么我们不再需要生命周期注释了。而且也没有索引来保存状态,我们很快就会看到原因。
impl IntoIterator for Todos {
type Item = Todo;
type IntoIter = TodosIntoIterator;
fn into_iter(self) -> TodosIntoIterator {
TodosIntoIterator { todos: self }
}
}
impl Iterator for TodosIntoIterator {
type Item = Todo;
fn next(&mut self) -> Option<Self::Item> {
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:
for todo in todo_list.iter() {
println!("{}", todo);// todo is a &Todo
}
for todo in todo_list {
println!("{}", todo); // todo is a Todo
}