简体   繁体   English

如何为简单的结构实现 Iterator 和 IntoIterator?

[英]How to implement Iterator and IntoIterator for a simple struct?

How would someone implement the Iterator and IntoIterator traits for the following struct?有人将如何为以下结构实现IteratorIntoIterator特征?

struct Pixel {
    r: i8,
    g: i8,
    b: i8,
}

I've tried various forms of the following with no success.我尝试了以下各种形式,但都没有成功。

impl IntoIterator for Pixel {
    type Item = i8;
    type IntoIter = Iterator<Item=Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        [&self.r, &self.b, &self.g].into_iter()
    }
}

This code gives me a compile error这段代码给了我一个编译错误

error[E0277]: the trait bound `std::iter::Iterator<Item=i8> + 'static: std::marker::Sized` is not satisfied
 --> src/main.rs:7:6
  |
7 | impl IntoIterator for Pixel {
  |      ^^^^^^^^^^^^ the trait `std::marker::Sized` is not implemented for `std::iter::Iterator<Item=i8> + 'static`
  |
  = note: `std::iter::Iterator<Item=i8> + 'static` does not have a constant size known at compile-time
  = note: required by `std::iter::IntoIterator`

Your iterator type is Iterator<Item = Self::Item> , but Iterator is a trait.您的迭代器类型是Iterator<Item = Self::Item> ,但Iterator是一个特征。 Traits are implemented by structs, they don't exist on their own.特征由结构实现,它们本身并不存在。 You could also have a reference trait object ( &Iterator ), a boxed trait object ( Box<Iterator> ) or an anonymous trait implementation ( impl Iterator ), all of which have a known sizes.你也可以有一个引用 trait 对象( &Iterator )、一个装箱的 trait 对象( Box<Iterator> )或一个匿名 trait 实现( impl Iterator ),所有这些都有一个已知的大小。

Instead, we create a PixelIntoIterator that has a known size and implements Iterator itself:相反,我们创建了一个PixelIntoIterator ,它具有已知的大小并实现Iterator本身:

struct Pixel {
    r: i8,
    g: i8,
    b: i8,
}

impl IntoIterator for Pixel {
    type Item = i8;
    type IntoIter = PixelIntoIterator;

    fn into_iter(self) -> Self::IntoIter {
        PixelIntoIterator {
            pixel: self,
            index: 0,
        }
    }
}

pub struct PixelIntoIterator {
    pixel: Pixel,
    index: usize,
}

impl Iterator for PixelIntoIterator {
    type Item = i8;
    fn next(&mut self) -> Option<i8> {
        let result = match self.index {
            0 => self.pixel.r,
            1 => self.pixel.g,
            2 => self.pixel.b,
            _ => return None,
        };
        self.index += 1;
        Some(result)
    }
}

fn main() {
    let p = Pixel {
        r: 54,
        g: 23,
        b: 74,
    };
    for component in p {
        println!("{}", component);
    }
}

This has the nice benefit of returning actual i8 s, not references.这具有返回实际i8而非引用的好处。 Since these are so small, you might as well pass them directly.由于这些很小,您不妨直接传递它们。

This consumes the Pixel .这会消耗Pixel If you had a reference to a Pixel , you'd need to also implement an iterator that doesn't consume it:如果您有对Pixel的引用,则还需要实现一个不使用它的迭代器:

impl<'a> IntoIterator for &'a Pixel {
    type Item = i8;
    type IntoIter = PixelIterator<'a>;

    fn into_iter(self) -> Self::IntoIter {
        PixelIterator {
            pixel: self,
            index: 0,
        }
    }
}

pub struct PixelIterator<'a> {
    pixel: &'a Pixel,
    index: usize,
}

impl<'a> Iterator for PixelIterator<'a> {
    type Item = i8;
    fn next(&mut self) -> Option<i8> {
        let result = match self.index {
            0 => self.pixel.r,
            1 => self.pixel.g,
            2 => self.pixel.b,
            _ => return None,
        };
        self.index += 1;
        Some(result)
    }
}

If you wanted to support creating both a consuming iterator and a non-consuming iterator, you can implement both versions.如果您想支持创建消费迭代器和非消费迭代器,您可以实现这两个版本。 You can always take a reference to a Pixel you own, so you only need the non-consuming variant.您始终可以引用您拥有的Pixel ,因此您只需要非消耗变量。 However, it's often nice to have a consuming version so that you can return the iterator without worrying about lifetimes.但是,拥有一个消费版本通常很好,这样您就可以返回迭代器而不必担心生命周期。


it'd be much more convenient to write this by reusing iterators that already exists, eg, with [T; 3]通过重用已经存在的迭代器来编写它会更方便,例如,使用[T; 3] [T; 3]

As of Rust 1.51, you can leverage array::IntoIter :从 Rust 1.51 开始,您可以利用array::IntoIter

impl IntoIterator for Pixel {
    type Item = i8;
    type IntoIter = std::array::IntoIter<i8, 3>;

    fn into_iter(self) -> Self::IntoIter {
        std::array::IntoIter::new([self.r, self.b, self.g])
    }
}

In previous versions, it might be a bit silly, but you could avoid creating your own iterator type by gluing some existing types together and using impl Iterator :在以前的版本中,这可能有点傻,但是您可以通过将一些现有类型粘合在一起并使用impl Iterator来避免创建自己的迭代器类型:

use std::iter;

impl Pixel {
    fn values(&self) -> impl Iterator<Item = i8> {
        let r = iter::once(self.r);
        let b = iter::once(self.b);
        let g = iter::once(self.g);
        r.chain(b).chain(g)
    }
}

First, IntoIter must point to a real struct and not to a trait in order for Rust to be able to pass the value around (that's what Sized means).首先, IntoIter必须指向一个真正的struct而不是一个trait ,以便 Rust 能够传递值(这就是Sized意思)。 In case of arrays into_iter returns the std::slice::Iter struct .如果是数组into_iter返回std::slice::Iter struct

Second, a typical array, [1, 2, 3] , isn't allocated on heap.其次,典型的数组[1, 2, 3]不在堆上分配。 In fact, the compiler is allowed to optimize away the allocation entirely, pointing to a pre-compiled array instead.事实上,编译器可以完全优化分配,改为指向预编译的数组。 Being able to iterate the arrays without copying them anywhere is I think the reason why the IntoIterator implementation for arrays doesn't move the array anywhere as other IntoIterator implementations do.能够迭代数组而不将它们复制到任何地方是我认为数组的IntoIterator实现不会像其他IntoIterator实现那样将数组移动到任何地方的IntoIterator Instead it seems to reference the existing array.相反,它似乎引用了现有数组。 You can see from its signature它的签名中可以看出

impl<'a, T> IntoIterator for &'a [T; 3]
    type Item = &'a T
    type IntoIter = Iter<'a, T>
    fn into_iter(self) -> Iter<'a, T>

that it takes a reference to an array ( &'a [T; 3] ).它需要对数组的引用&'a [T; 3] )。

As such, you can't use it in the way you're trying to.因此,您不能以您想要的方式使用它。 The referenced array must outlive the returned iterator.被引用的数组必须比返回的迭代器寿命更长。 Here's a version where Rust compiler tells so.这是Rust 编译器告诉我们的一个版本

Vector has an IntoIterator implementation that truly moves the data into the iterator and so you can use it . Vector 有一个IntoIterator实现,它真正将数据移动到迭代器中,因此您可以使用它


PS To make it both fast and simple, return an array instead of an iterator ( playpen ): PS 为了既快速又简单,返回一个数组而不是迭代器( playpen ):

impl Pixel {
    fn into_array(self) -> [i8; 3] {[self.r, self.g, self.b]}
}

That way the array is first moved into the outer scope and then it can be referenced from the outer scope's iterator:这样数组首先被移动到外部作用域,然后它可以被外部作用域的迭代器引用

for color in &(Pixel {r: 1, g: 2, b: 3}).into_array() {
    println! ("{}", color);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM