关于rust:如何为简单的结构实现Iterator和IntoIterator?

How to implement Iterator and IntoIterator for a simple struct?

有人将如何为以下结构实现IteratorIntoIterator特征?

1
2
3
4
5
struct Pixel {
    r: i8,
    g: i8,
    b: i8,
}

我尝试了以下各种形式,但均未成功。

1
2
3
4
5
6
7
8
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()
    }
}

这段代码给我一个编译错误

1
2
3
4
5
6
7
8
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`


您的迭代器类型为Iterator,但是Iterator是特征。特性是由结构实现的,它们并不独立存在。您还可以具有参考特征对象(&Iterator),装箱特征对象(Box)或匿名特征实现(impl Iterator),所有这些都有已知的大小。

相反,我们创建一个具有已知大小的PixelIntoIterator并实现Iterator本身:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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,
        }
    }
}

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);
    }
}

这具有返回实际i8而不是引用的好处。由于它们很小,您不妨直接通过它们。

这会消耗Pixel。如果您引用了Pixel,则还需要实现一个不使用它的迭代器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
impl<'a> IntoIterator for &'a Pixel {
    type Item = i8;
    type IntoIter = PixelIterator<'a>;

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

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)
    }
}

如果要支持同时创建使用迭代器和非使用迭代器,则可以实现这两个版本。您始终可以引用自己拥有的Pixel,因此只需要非消耗型。但是,拥有使用版本通常很不错,这样您就可以返回迭代器而不必担心生命周期。

这可能有点愚蠢,但是您可以通过将一些现有类型粘合在一起并使用impl Iterator来避免创建自己的迭代器类型:

1
2
3
4
5
6
7
8
9
10
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)
    }
}


首先,IntoIter必须指向真实的struct而不是trait,以使Rust能够传递值(这就是Sized的意思)。如果是数组into_iter,则返回std :: slice :: Iter struct

其次,典型的数组[1, 2, 3]不在堆上分配。实际上,允许编译器完全优化分配,而是指向预编译的数组。我认为能够迭代数组而不将其复制到任何地方是我为什么数组的IntoIterator实现不能像其他IntoIterator实现那样将数组移到任何地方的原因。相反,它似乎引用了现有的数组。您可以从其签名中看到

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

它需要引用数组(&'a [T; 3])。

因此,您不能以尝试的方式使用它。引用的数组必须比返回的迭代器有效。这是Rust编译器告诉您的版本。

Vector具有IntoIterator实现,可以将数据真正移到迭代器中,因此您可以使用它。

附言为了既简单又快速,请返回一个数组,而不是一个迭代器(游戏围栏):

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

这样,数组首先移入外部作用域,然后可以从外部作用域的迭代器进行引用:

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