使用泛型迭代器而不是特定的列表类型

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()Iteratortrait中定义,但它也可以是一个独立的函数:

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()只返回调用它的迭代器),因此您也可以Iteratorfor循环中使用实例.

因此,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对象类型中指定它们(否则生命周期信息将丢失).

Iteratortrait 创建的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)

  • 好吧,可以克隆一些迭代器*,但是你链接到的示例没有"克隆迭代器".`cloned()`只是另一个迭代器转换方法,在这里[http://doc.rust-lang.org/std/iter/trait.Iterator.html#method.cloned]进行了描述.如果`T`是可克隆的,从`Iterator <Item =&T>`获取`Iterator <Item = T>`是很有用的. (2认同)

Vee*_*rac 6

这是 的完整版本Map这是构建它的函数。

一个最小的实现看起来像

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,只是代替了GetEnumeratorinto_iter