web-dev-qa-db-fra.com

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

Comment quelqu'un pourrait-il implémenter 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`
42
Piper Merriam

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

Au lieu de cela, nous créons un PixelIntoIterator qui a une taille connue et implémenteIterator 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 présente l'avantage de renvoyer i8s, pas des références. Comme ils sont si petits, vous pourriez aussi bien les passer directement.

Cela consomme le Pixel. Si vous aviez une référence à un Pixel, vous devez é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 avez seulement besoin la variante non consommatrice. Cependant, il est souvent agréable d'avoir une version consommatrice afin que vous puissiez retourner l'itérateur sans vous soucier de la durée de vie.


Cela peut être un peu idiot, mais vous pouvez éviter de créer votre propre type d'itérateur en collant certains types existants ensemble et en utilisant 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)
    }
}
67
Shepmaster

Tout d'abord, IntoIter doit pointer vers un vrai struct et non vers un trait pour que Rust puisse transmettre la valeur autour (c'est ce que signifie Sized). Dans le cas de tableaux into_iter renvoie le std :: slice :: Iterstruct.

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 plutôt vers un tableau précompilé. Pouvoir itérer les tableaux sans les copier n'importe où est, je pense, la raison pour laquelle l'implémentation de 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 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 prend une référence à un tableau (&'a [T; 3]).

En tant que tel, vous ne pouvez pas l'utiliser comme vous le souhaitez. Le tableau référencé doit survivre à l'itérateur renvoyé. Voici une version où Rust le dit.

Le vecteur a une implémentation IntoIterator qui déplace vraiment les données dans l'itérateur et donc vous pouvez l'utiliser .


P.S. Pour le rendre à la fois rapide et simple, renvoyez 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é depuis l'itérateur de la portée externe:

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