我是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
类型,在文档中可能更多,但没有理解它们的好方法 .
任何人都可以提供如何创建上述内容的明确示例吗?谢谢!
附:懒惰的方面是可选的 . 我更关心远离特定列表和数组类型的抽象 .
3 回答
首先,忘记
IntoIterator
和其他特征或类型 . Rust的核心迭代特性是Iterator
. 其修剪定义如下:您可能知道,您可以将迭代器视为某个结构中的游标 .
next()
方法向前推进此光标,返回它先前指向的元素 . 当然,如果集合耗尽,则无法返回,因此next()
返回Option<Self::Item>
,而不仅仅是Self::Item
.Iterator
是一个特征,因此可以通过特定类型实现 . 请注意,Iterator
本身不是一个可以用作返回值或函数参数的正确类型 - 您必须使用实现此特征的具体类型 .上面的陈述可能听起来过于严格 - 如何使用任意迭代器类型呢? - 但由于泛型,事实并非如此 . 如果希望函数接受任意迭代器,只需在相应的参数中使其成为通用,在相应的类型参数上添加
Iterator
绑定:从函数返回迭代器可能很困难,但请参见下文 .
例如,在
&[T]
上有一个名为iter()
的方法,它返回一个迭代器,该迭代器产生对切片的引用 . 此迭代器是this结构的实例 . 您可以在该页面上看到如何为Iter
实施Iterator
:此结构包含对原始切片的引用以及其中的一些迭代状态 . 它的
next()
方法更新此状态并返回下一个值(如果有) .任何类型实现
Iterator
的值都可以在for
循环中使用(for
循环实际上与IntoIterator
一起使用,但请参见下文):现在,
Iterator
特性实际上比上面的特征更复杂 . 它还定义了许多转换方法,它们使用它们被调用的迭代器并返回一个新的迭代器,它以某种方式转换或过滤原始迭代器中的值 . 例如,enumerate()
方法返回一个迭代器,它从原始迭代器中获取值以及元素的位置号:enumerate()
的定义如下:Enumerate
只是一个结构,其中包含一个迭代器和一个计数器,它实现Iterator<Item=(usize, I::Item)>
:这就是大多数迭代器转换的实现方式:每个转换都是一个包装结构,它包装原始迭代器并通过委托给原始迭代器并以某种方式转换结果值来实现
Iterator
trait . 例如,上例中的s.iter().enumerate()
返回类型Enumerate<Iter<'static, u8>>
的值 .请注意,虽然
enumerate()
直接在Iterator
trait中定义,但它也可以是一个独立的函数:该方法的工作方式非常相似 - 它只使用隐式
Self
类型参数而不是显式命名参数 .你可能想知道
IntoIterator
特质是什么 . 好吧,它只是一个便利转换特性,可以通过任何可以转换为迭代器的类型来实现:例如,
&'a [T]
可以转换为Iter<'a, T>
,因此它具有以下实现:此特征是针对大多数容器类型和对这些类型的引用实现的 . 它实际上由
for
循环使用 - 实现IntoIterator
的任何类型的值都可以在in
子句中使用:这在学习和阅读方面非常好,因为它具有较少的噪音(以类似于
iter()
的方式) . 它甚至允许这样的事情:这是可能的,因为对于
&Vec<T>
,&mut Vec<T>
和Vec<T>
,IntoIterator
的实现方式不同 .每个
Iterator
实现IntoIterator
执行身份转换(into_iter()
只返回它被调用的迭代器),因此您也可以在for
循环中使用Iterator
实例 .因此,在通用函数中使用
IntoIterator
是有意义的,因为它将使API更方便用户 . 例如,上面的enumerate()
函数可以这样重写:现在,您可以看到如何使用泛型来轻松实现静态类型转换 . Rust没有像C#/ Python
yield
这样的东西(但它是最理想的功能之一,所以有一天它可能出现在语言中!),因此你需要显式地包装源迭代器 . 例如,您可以编写类似于上述Enumerate
结构的内容,它可以完成您想要的任务 .但是,最常用的方法是使用现有的组合器为您完成工作 . 例如,您的代码可能写成如下:
但是,当你想编写自定义组合函数时,使用组合器可能会变得丑陋,因为许多现有的组合函数接受闭包(例如上面的
filter()
),但Rust中的闭包是作为匿名类型的值实现的,所以根本没办法写入返回迭代器的函数的签名:有几种方法,其中一种方法是使用特征对象:
这里我们隐藏
filter()
在特征对象后面返回的实际迭代器类型 . 请注意,为了使函数完全通用,我必须添加一个生命周期参数和相应的绑定到Box
特征对象和I::IntoIter
关联类型 . 这是必要的,因为I::IntoIter
可能在其中包含任意生命周期(就像上面的Iter<'a, T>
类型一样),我们必须在特征对象类型中指定它们(否则生命周期信息将丢失) .从
Iterator
trait创建的特征对象实现Iterator
本身,因此您可以像往常一样继续使用这些迭代器:Here is the full version of Map和here is the function that builds it.
最小的实现看起来像
Playpen link.注意迭代器中使用的
map
是Option
上的方法;这不是递归定义的!这写起来不太方便,但是男孩很快!
现在,要为任意"enumerable"类型写这个,可以将
map
更改为IntoIterator
基本上是IEnumerable
,而不是GetEnumerator
那里有into_iter
.为应该充当迭代器的结构实现Iterator特征 . 您只需要实现
next
方法 . 其他方法具有默认实现 .无法创建适用于任何容器的迭代器 . 此类所需的类型系统机械尚不存在 .