首先,忘记关于IntoIterator
和其他特征或类型的事情。在Rust中,核心迭代特征是Iterator
。它削减后的定义如下:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
正如您可能知道的那样,您可以将迭代器视为某些结构内部的光标。 next()
方法将此光标向前移动,返回它先前指向的元素。当然,如果集合已耗尽,则没有要返回的内容,因此next()
返回Option<Self::Item>
而不仅仅是Self::Item
。
Iterator
是一个特征,因此可以由特定类型实现。请注意,Iterator
本身不是可用作返回值或函数参数的正确类型-必须使用实现此特征的具体类型。
上述语句可能听起来太过严格-那么如何使用任意迭代器类型呢?但由于泛型,这并不是问题。如果要使函数接受任意迭代器,请在相应的参数中使其成为通用类型,添加对应类型参数的Iterator
绑定:
fn iterate_bytes<I>(iter: I) where I: Iterator<Item=u8> { ... }
从函数返回迭代器可能会比较困难,但请见下文。
例如,&[T]
上有一个名为 iter()
的方法,它返回一个迭代器,该迭代器产生对切片中的引用。这个迭代器是 这个 结构体的实例。您可以在该页面上查看如何为 Iter
实现 Iterator
。
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> { ... }
...
}
这个结构体保存了对原始切片的引用以及其中的一些迭代状态。它的next()
方法会更新该状态并返回下一个值(如果有)。
任何类型实现了Iterator
的值都可以在for
循环中使用(实际上,for
循环使用的是IntoIterator
,但详见下文):
let s: &[u8] = b"hello";
for b in s.iter() {
println!("{}", b);
}
现在,
Iterator
特质实际上比上面的更加复杂。它还定义了许多转换方法,这些方法会消耗它们所调用的迭代器,并返回一个新的迭代器,该迭代器以某种方式从原始迭代器中转换或过滤值。例如,
enumerate()
方法返回一个迭代器,该迭代器与元素的位置编号一起产生原始迭代器的值:
let s: &[u8] = b"hello";
for (i, b) in s.iter().enumerate() {
println!("{} at {}", b, i);
}
enumerate()
的定义如下:
trait Iterator {
type Item;
...
fn enumerate(self) -> Enumerate<Self> {
Enumerate {
iter: self,
count: 0
}
}
...
}
Enumerate
是一个包含迭代器和计数器的结构体,并且实现了 Iterator<Item=(usize, I::Item)>
接口:
struct Enumerate<I> {
iter: I,
count: usize
}
impl<I> Iterator for Enumerate<I> where I: Iterator {
type Item = (usize, I::Item);
#[inline]
fn next(&mut self) -> Option<(usize, I::Item)> {
self.iter.next().map(|a| {
let ret = (self.count, a);
self.count += 1;
ret
})
}
}
这是大多数迭代器转换的实现方式:每个转换都是一个包装结构体,它包装原始迭代器并通过委托给原始迭代器来实现Iterator
trait和某种程度的值变换。例如,上面的例子中的 s.iter().enumerate()
返回一个类型为 Enumerate<Iter<'static, u8>>
的值。
请注意,虽然 enumerate()
直接在 Iterator
trait 中定义,但它也可以是一个独立的函数:
fn enumerate<I>(iter: I) -> Enumerate<I> where I: Iterator {
Enumerate {
iter: iter,
count: 0
}
}
这种方法的工作方式非常相似-它只是使用隐式的Self
类型参数而不是显式命名的参数。
你可能会想知道IntoIterator
trait是什么。嗯,它只是一个方便的转换trait,可以由任何可以转换为迭代器的类型来实现:
pub trait IntoIterator where Self::IntoIter::Item == Self::Item {
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
例如,&'a [T]
可以转换为 Iter<'a, T>
,因此它具有以下实现:
impl<'a, T> IntoIterator for &'a [T] {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Iter<'a, T> {
self.iter()
}
}
这个特性已经被大多数容器类型和对这些类型的引用所实现。事实上,for
循环就是使用了它——任何实现了 IntoIterator
特性的类型的值都可以在 in
子句中使用:
let s: &[u8] = b"hello";
for b in s { ... }
从学习和阅读的角度来看,这非常好,因为它具有更少的噪音(以iter()
-like方法的形式)。它甚至允许像这样的事情:
let v: Vec<u8> = ...;
for i in &v { }
for i in &mut v { }
for i in v { }
这是因为
&Vec<T>
、
&mut Vec<T>
和
Vec<T>
针对
IntoIterator
实现是不同的。每个
Iterator
都实现了
IntoIterator
,它执行一个身份转换(
into_iter()
只是返回调用它的迭代器),所以你也可以在
for
循环中使用
Iterator
实例。因此,在通用函数中使用
IntoIterator
是有意义的,因为它会让API更加方便易用。例如,上面的
enumerate()
函数可以重写为:
fn enumerate<I>(source: I) -> Enumerate<I::IntoIter> where I: IntoIterator {
Enumerate {
iter: source.into_iter(),
count: 0
}
}
现在,您可以看到如何使用泛型轻松地实现具有静态类型的转换。Rust 没有像 C# / Python 的 yield
这样的功能(但它是最受欢迎的功能之一,因此它可能会出现在语言中!),因此您需要明确地包装源迭代器。例如,您可以编写类似于上面的 Enumerate
结构,该结构执行所需的任务。
然而,最惯用的方法是使用现有的组合器来为您完成工作。例如,您的代码可以编写如下:
let iter = ...;
let r = iter.filter(|&x| x % 2 == 0);
for i in r {
println!("{}", i);
}
然而,在编写自定义组合函数时,使用组合子可能会变得难看,因为许多现有的组合函数接受闭包(例如上面的filter()
),但是在Rust中,闭包实现为匿名类型的值,因此根本没有办法编写返回迭代器的函数签名:
fn filter_even<I>(source: I) -> ??? where I: IntoIter<Item=i32> {
source.into_iter().filter(|&x| x % 2 == 0)
}
有几种方法可以解决这个问题,其中之一是使用特质对象:
fn filter_even<'a, I>(source: I) -> Box<Iterator<Item=i32>+'a>
where I: IntoIterator<Item=i32>, I::IntoIter: 'a
{
Box::new(source.into_iter().filter(|&x| x % 2 == 0))
}
在这里,我们使用特质对象隐藏了由filter()
返回的实际迭代器类型。请注意,为了使函数完全通用,我不得不添加一个生命周期参数以及相应的边界到Box
特征对象和I::IntoIter
关联类型中。这是必要的,因为I::IntoIter
可能包含其中任意的生命周期(就像上面的Iter<'a, T>
类型一样),并且我们必须在特质对象类型中指定它们(否则生命周期信息将会丢失)。
从Iterator
特质创建的特质对象本身实现了Iterator
,因此您可以像往常一样继续使用这些迭代器:
let source = vec![1_i32, 2, 3, 4];
for i in filter_even(source) {
println!("{}", i);
}
yield
关键字。Rust并没有完全拥有这些,但是您应该能够使用Iterator
完成所有相同的事情。尽管实现迭代器时可能会更加复杂,但可以将其打出来。 - ShepmasterIterator
如何帮助解决这个问题。 - jocull