joc*_*ull 10 arrays iterator vector rust
我是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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
这当然是一个愚蠢的例子.我知道我可以用Rust的map
函数做到这一点,但我想知道如何创建自己的接受和返回泛型迭代器的方法.
从我可以收集到的内容,Rust具有可以类似使用的泛型迭代器,但它们超出了我的理解.我看到Iter
,IntoIterator
,Iterator
类型,以及可能更多的文档,但没有很好地理解他们.
任何人都可以提供如何创建上述内容的明确示例吗?谢谢!
PS懒惰的方面是可选的.我更关心远离特定列表和数组类型的抽象.
Vla*_*eev 16
首先,忘记IntoIterator
和其他特征或类型.Rust的核心迭代特性是Iterator
.其修剪定义如下:
trait Iterator {
type Item; // type of elements returned by the iterator
fn next(&mut self) -> Option<Self::Item>;
}
Run Code Online (Sandbox Code Playgroud)
您可能知道,您可以将迭代器视为某个结构中的游标.next()
方法向前推进此光标,返回它先前指向的元素.当然,如果收集用尽,没有什么可以返回,所以next()
返回Option<Self::Item>
,而不仅仅是Self::Item
.
Iterator
是一种特性,因此可以通过特定类型实现.请注意,Iterator
它本身不是一个可以用作返回值或函数参数的正确类型 - 您必须使用实现此特征的具体类型.
上面的陈述可能听起来过于严格 - 如何使用任意迭代器类型呢?- 但由于泛型,事实并非如此.如果你想要一个函数接受任意迭代器,只需在相应的参数中使它成为泛型,在相应Iterator
的类型参数上添加一个绑定:
fn iterate_bytes<I>(iter: I) where I: Iterator<Item=u8> { ... }
Run Code Online (Sandbox Code Playgroud)
从函数返回迭代器可能很困难,但请参见下文.
例如,有一个方法on &[T]
,called iter()
,它返回一个迭代器,它产生对切片的引用.此迭代器是此结构的一个实例.您可以在该页面上看到如何Iterator
实现Iter
:
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> { ... }
...
}
Run Code Online (Sandbox Code Playgroud)
此结构包含对原始切片的引用以及其中的一些迭代状态.它的next()
方法更新此状态并返回下一个值(如果有).
任何类型实现的值Iterator
都可以在for
循环中使用(for
循环实际上可以使用IntoIterator
,但请参见下文):
let s: &[u8] = b"hello";
for b in s.iter() {
println!("{}", b); // prints numerical value of each byte
}
Run Code Online (Sandbox Code Playgroud)
现在,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.
}
Run Code Online (Sandbox Code Playgroud)
enumerate()
定义如下:
trait Iterator {
type Item;
...
fn enumerate(self) -> Enumerate<Self> {
Enumerate {
iter: self,
count: 0
}
}
...
}
Run Code Online (Sandbox Code Playgroud)
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
})
}
}
Run Code Online (Sandbox Code Playgroud)
而这是大多数迭代转变是如何实现的:每个转型是一个包装结构包装了原来的迭代器,并实现Iterator
通过委派到原来的迭代器并以某种方式转化所产生的价值特征.例如,s.iter().enumerate()
从上面的示例中返回type的值Enumerate<Iter<'static, u8>>
.
请注意,虽然直接enumerate()
在Iterator
trait中定义,但它也可以是一个独立的函数:
fn enumerate<I>(iter: I) -> Enumerate<I> where I: Iterator {
Enumerate {
iter: iter,
count: 0
}
}
Run Code Online (Sandbox Code Playgroud)
该方法的工作方式非常相似 - 它只使用隐式Self
类型参数而不是显式命名的参数.
你可能想知道什么是IntoIterator
特质.好吧,它只是一个便利转换特性,可以通过任何可以转换为迭代器的类型来实现:
pub trait IntoIterator where Self::IntoIter::Item == Self::Item {
type Item;
type IntoIter: Iterator;
fn into_iter(self) -> Self::IntoIter;
}
Run Code Online (Sandbox Code Playgroud)
例如,&'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
}
}
Run Code Online (Sandbox Code Playgroud)
此特征是针对大多数容器类型和对这些类型的引用实现的.它实际上是由for
循环使用 - 实现的任何类型的值IntoIterator
都可以在in
子句中使用:
let s: &[u8] = b"hello";
for b in s { ... }
Run Code Online (Sandbox Code Playgroud)
从学习和阅读的角度来看,这是非常好的,因为它具有较少的噪音(以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 */ }
Run Code Online (Sandbox Code Playgroud)
因为这是有可能IntoIterator
实现不同的&Vec<T>
,&mut Vec<T>
和刚Vec<T>
.
每个Iterator
执行IntoIterator
身份转换的实现(into_iter()
只返回调用它的迭代器),因此您也可以Iterator
在for
循环中使用实例.
因此,IntoIterator
在通用函数中使用是有意义的,因为它将使API对用户更方便.例如,enumerate()
上面的函数可以重写为:
fn enumerate<I>(source: I) -> Enumerate<I::IntoIter> where I: IntoIter {
Enumerate {
iter: source.into_iter(),
count: 0
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以看到如何使用泛型来轻松实现静态类型转换.Rust没有像C#/ Python那样的东西yield
(但它是最理想的功能之一,所以有一天它可能出现在语言中!),因此你需要显式地包装源迭代器.例如,您可以编写类似于上述Enumerate
结构的内容来完成您想要的任务.
但是,最常用的方法是使用现有的组合器为您完成工作.例如,您的代码可能写成如下:
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
}
Run Code Online (Sandbox Code Playgroud)
但是,当你想编写自定义组合函数时,使用组合器可能会变得丑陋,因为很多现有的组合函数接受闭包(例如filter()
上面的那个),但Rust中的闭包是作为匿名类型的值实现的,所以没有办法写出返回迭代器的函数的签名:
fn filter_even<I>(source: I) -> ??? where I: IntoIter<Item=i32> {
source.into_iter().filter(|&x| x % 2 == 0)
}
Run Code Online (Sandbox Code Playgroud)
有几种方法,其中一种方法是使用特征对象:
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))
}
Run Code Online (Sandbox Code Playgroud)
这里我们隐藏filter()
trait对象后面返回的实际迭代器类型.请注意,为了使函数完全通用,我必须添加一个生命周期参数和一个对应于Box
特征对象和I::IntoIter
相关类型的绑定.这是必要的,因为I::IntoIter
它内部可能包含任意生命周期(就像Iter<'a, T>
上面的类型一样),我们必须在trait对象类型中指定它们(否则生命周期信息将丢失).
从Iterator
trait 创建的trait 对象实现Iterator
自己,所以你可以像往常一样继续使用这些迭代器:
let source = vec![1_i32, 2, 3, 4];
for i in filter_even(source) {
println!("{}", i); // prints 2 and 4
}
Run Code Online (Sandbox Code Playgroud)
一个最小的实现看起来像
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))
}
}
Run Code Online (Sandbox Code Playgroud)
婴儿围栏链接。请注意,map
迭代器内部使用的是 on 方法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}
}
Run Code Online (Sandbox Code Playgroud)
IntoIterator
基本上IEnumerable
,只是代替了GetEnumerator
有into_iter
。