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

时间:2015-06-03 21:10:53

标签: arrays vector iterator 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;
        }
    }
}

这当然是一个愚蠢的例子。我知道我可以使用Rust的map函数执行此操作,但我想知道如何创建自己的接受和返回泛型迭代器的方法。

从我可以收集到的内容,Rust具有可以类似使用的泛型迭代器,但它们超出了我的理解。我看到IterIntoIteratorIterator类型,可能还有更多文档,但没有好方法可以理解它们。

任何人都可以提供如何创建上述内容的明确示例吗?谢谢!

P.S。懒惰的方面是可选的。我更关心远离特定列表和数组类型的抽象。

3 个答案:

答案 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>实现IteratorIntoIterator只返回它被调用的迭代器),因此您可以在{{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 Maphere 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.请注意,迭代器中使用的mapOption上的方法;这不是递归定义的!

写作不太方便,但是男孩很快!

现在,要为任意“可枚举”类型编写此内容,可以将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方法。其他方法都有默认实现。

无法创建适用于任何容器的迭代器。为此所需的类型系统机制尚不存在。