首页 文章

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

提问于
浏览
8

我是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 类型,在文档中可能更多,但没有理解它们的好方法 .

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

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

3 回答

  • 3

    首先,忘记 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结构的实例 . 您可以在该页面上看到如何为 Iter 实施 Iterator

    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 trait . 例如,上例中的 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 */ }
    

    这是可能的,因为对于 &Vec<T>&mut Vec<T>Vec<T>IntoIterator 的实现方式不同 .

    每个 Iterator 实现 IntoIterator 执行身份转换( into_iter() 只返回它被调用的迭代器),因此您也可以在 for 循环中使用 Iterator 实例 .

    因此,在通用函数中使用 IntoIterator 是有意义的,因为它将使API更方便用户 . 例如,上面的 enumerate() 函数可以这样重写:

    fn enumerate<I>(source: I) -> Enumerate<I::IntoIter> where I: IntoIter {
        Enumerate {
            iter: source.into_iter(),
            count: 0
        }
    }
    

    现在,您可以看到如何使用泛型来轻松实现静态类型转换 . 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
    }
    

    但是,当你想编写自定义组合函数时,使用组合器可能会变得丑陋,因为许多现有的组合函数接受闭包(例如上面的 filter() ),但Rust中的闭包是作为匿名类型的值实现的,所以根本没办法写入返回迭代器的函数的签名:

    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 关联类型 . 这是必要的,因为 I::IntoIter 可能在其中包含任意生命周期(就像上面的 Iter<'a, T> 类型一样),我们必须在特征对象类型中指定它们(否则生命周期信息将丢失) .

    Iterator trait创建的特征对象实现 Iterator 本身,因此您可以像往常一样继续使用这些迭代器:

    let source = vec![1_i32, 2, 3, 4];
    for i in filter_even(source) {
        println!("{}", i);  // prints 2 and 4
    }
    
  • 0

    Here is the full version of Maphere is the function that builds it.

    最小的实现看起来像

    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 上的方法;这不是递归定义的!

    这写起来不太方便,但是男孩很快!


    现在,要为任意"enumerable"类型写这个,可以将 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 .

  • 13

    为应该充当迭代器的结构实现Iterator特征 . 您只需要实现 next 方法 . 其他方法具有默认实现 .

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

相关问题