我是Rust的新手,来自C#/ Java /类似。
在C#中,我们有IEnumerable<T>
可用于迭代几乎任何类型的数组或列表。 C#还有一个yield
关键字,可用于返回惰性列表。这是一个例子......
// Lazily returns the even numbers out of an enumerable
IEnumerable<int> Evens(IEnumerable<int> input)
{
foreach (var x in input)
{
if (x % 2 == 0)
{
yield return x;
}
}
}
这当然是一个愚蠢的例子。我知道我可以使用Rust的map
函数执行此操作,但我想知道如何创建自己的接受和返回泛型迭代器的方法。
从我可以收集到的内容,Rust具有可以类似使用的泛型迭代器,但它们超出了我的理解。我看到Iter
,IntoIterator
,Iterator
类型,可能还有更多文档,但没有好方法可以理解它们。
任何人都可以提供如何创建上述内容的明确示例吗?谢谢!
P.S。懒惰的方面是可选的。我更关心远离特定列表和数组类型的抽象。
答案 0 :(得分:14)
首先,忘记IntoIterator
和其他特征或类型。 Rust中的核心迭代特征是Iterator
。其精简定义如下:
trait Iterator {
type Item; // type of elements returned by the iterator
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()
的方法,它返回一个迭代器,该迭代器产生对切片的引用。此迭代器是this结构的实例。您可以在该页面上看到Iterator
:{/ 1>的Iter
实施情况
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); // prints numerical value of each byte
}
现在,Iterator
特征实际上比上面的特征更复杂。它还定义了许多转换方法,它们使用它们被调用的迭代器并返回一个新的迭代器,它以某种方式转换或过滤原始迭代器中的值。例如,enumerate()
方法返回一个迭代器,它从原始迭代器中获取值以及元素的位置编号:
let s: &[u8] = b"hello";
for (i, b) in s.iter().enumerate() {
println!("{} at {}", b, i); // prints "x at 0", "y at 1", etc.
}
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
特征不知何故。例如,上例中的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
特质是什么。好吧,它只是一个便利转换特性,可以通过任何可以转换为迭代器的类型来实现:
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() // just delegate to the existing method
}
}
此特征适用于大多数容器类型和对这些类型的引用。它实际上由for
循环使用 - 实现IntoIterator
的任何类型的值都可以在in
子句中使用:
let s: &[u8] = b"hello";
for b in s { ... }
从学习和阅读的角度来看,这是非常好的,因为它具有较少的噪音(以iter()
形式 - 类似方法)。它甚至允许这样的事情:
let v: Vec<u8> = ...;
for i in &v { /* i is &u8 here, v is borrowed immutably */ }
for i in &mut v { /* i is &mut u8 here, v is borrowed mutably */ }
for i in v { /* i is just u8 here, v is consumed */ }
这是可能的,因为IntoIterator
,&Vec<T>
和&mut Vec<T>
的{{1}}实施方式不同。
执行身份转换的每个Vec<T>
实现Iterator
(IntoIterator
只返回它被调用的迭代器),因此您可以在{{1}中使用into_iter()
个实例也是循环。
因此,在通用函数中使用Iterator
是有意义的,因为它将使API更方便用户。例如,上面的for
函数可以这样重写:
IntoIterator
现在,您可以看到如何使用泛型来轻松实现静态类型转换。 Rust没有像C#/ Python enumerate()
这样的东西(但它是最理想的功能之一,所以有一天它可能出现在语言中!),因此你需要显式地包装源迭代器。例如,您可以编写类似于上述fn enumerate<I>(source: I) -> Enumerate<I::IntoIter> where I: IntoIter {
Enumerate {
iter: source.into_iter(),
count: 0
}
}
结构的内容来执行您想要的任务。
但是,最惯用的方法是使用现有的组合器为您完成工作。例如,您的代码可能写成如下:
yield
但是,当你想编写自定义组合函数时,使用组合器可能会变得难看,因为很多现有的组合函数都接受闭包(例如上面的Enumerate
),但Rust中的闭包是作为匿名类型的值实现的,所以没有办法写出返回迭代器的函数的签名:
let iter = ...; // iter implements Iterator<Item=i32>
let r = iter.filter(|&x| x % 2 == 0); // r implements Iterator<Item=i32>
for i in r {
println!("{}", i); // prints only even items from the iterator
}
有几种解决方法,其中一种方法是使用特征对象:
filter()
这里我们隐藏了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
类型一样),我们必须在trait对象类型中指定它们(否则生命周期信息将丢失)。
从I::IntoIter
特征实现Iter<'a, T>
自己创建特征对象,因此您可以像往常一样继续使用这些迭代器:
Iterator
答案 1 :(得分:3)
Here is the full version of Map
和here is the function that builds it.
minimal 实现看起来像
fn map<I, E, B, F>(i: I, f: F) -> Map<I, F> where
F: FnMut(E) -> B,
I: Iterator<Item=E>
{
Map {iter: i, f: f}
}
pub struct Map<I, F> {
iter: I,
f: F,
}
impl<B, I: Iterator, F> Iterator for Map<I, F> where F: FnMut(I::Item) -> B {
type Item = B;
fn next(&mut self) -> Option<B> {
self.iter.next().map(|a| (self.f)(a))
}
}
Playpen link.请注意,迭代器中使用的map
是Option
上的方法;这不是递归定义的!
写作不太方便,但是男孩很快!
现在,要为任意“可枚举”类型编写此内容,可以将map
更改为
fn map<I, E, B, F>(i: I, f: F) -> Map<I::IntoIter, F> where
F: FnMut(E) -> B,
I: IntoIterator<Item=E>
{
Map {iter: i.into_iter(), f: f}
}
IntoIterator
基本上是IEnumerable
,而不是GetEnumerator
into_iter
。
答案 2 :(得分:0)
为应该充当迭代器的结构实现Iterator特征。您只需要实现next
方法。其他方法都有默认实现。
无法创建适用于任何容器的迭代器。为此所需的类型系统机制尚不存在。