Comment implémenter Iterator et Intoierator pour une structure simple?

Comment quelqu'un implémenterait-il les traits Iterator et IntoIterator pour la structure suivante?

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

J'ai essayé diverses formes de ce qui suit sans succès.

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

Ce code me donne une erreur de compilation

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`
31
demandé sur Shepmaster 2015-05-13 18:11:32

2 réponses

Votre type d'itérateur est Iterator<Item = Self::Item>, mais Iterator est un trait. Les Traits sont implémentés par des structures, ils n'existent pas seuls. Vous pouvez également avoir un objet trait de référence (&Iterator) ou un objet trait encadré (Box<Iterator>), qui ont tous deux une taille connue.

Au lieu de cela, nous créons un {[6] } qui a une taille connue et implémente Iterator lui-même:

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

Cela a l'avantage de renvoyer des i8réels, pas des références. Depuis qu'ils sont si petits, vous pourriez aussi bien passer eux directement.

Cela consomme le Pixel. Si vous aviez une référence à un Pixel, vous devrez également implémenter un itérateur qui ne le consomme pas:

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

Si vous souhaitez prendre en charge la création d'un itérateur consommateur et d'un itérateur Non consommateur, vous pouvez implémenter les deux versions. Vous pouvez toujours prendre une référence à un Pixel que vous possédez, donc vous n'avez besoin que de la variante non consommatrice. Cependant, il est souvent agréable d'avoir une version consommatrice afin que vous puissiez retourner l'itérateur sans se soucier de la durée de vie.

46
répondu Shepmaster 2017-07-21 16:25:21

Tout d'abord, {[3] } doit pointer vers un struct réel et non vers un trait pour que Rust puisse transmettre la valeur (c'est ce que Sized signifie). Dans le cas de matrices into_iter retourne la std::trancher::Iter struct.

Deuxièmement, un tableau typique, [1, 2, 3], n'est pas alloué sur le tas. En fait, le compilateur est autorisé à optimiser entièrement l'allocation, pointant vers un tableau pré-compilé à la place. Être capable d'itérer les tableaux sans les copier n'importe où est je pense la raison pour laquelle l'implémentation IntoIterator pour les tableaux ne déplace pas le tableau n'importe où comme le font les autres implémentations IntoIterator. Au lieu de cela, il semble référencer le tableau existant. Vous pouvez voir à partir de sa 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>

Qu'il faut une référence à un tableau (&'a [T; 3]).

En tant que tel, vous ne pouvez pas l'utiliser comme vous essayez de le faire. Le tableau référencé doit survivre à l'itérateur retourné. Voici une version où le compilateur Rust indique si.

Vecteur a une IntoIterator mise en œuvre qui touche réellement les données dans l'itérateur et donc , vous pouvez utiliser.


P.S. pour le rendre à la fois rapide et simple, retournez un tableau au lieu d'un itérateur ( parc):

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

De cette façon, le tableau est d'abord déplacé dans la portée externe, puis il peut être référencé à partir de l'itérateur de la portée externe:

for color in &(Pixel {r: 1, g: 2, b: 3}).into_array() {
    println! ("{}", color);
}
2
répondu ArtemGr 2015-05-13 18:12:43