Подтвердить что ты не робот

Как реализовать Iterator и IntoIterator для простой структуры?

Как кто-то реализует черты Iterator и IntoIterator для следующей структуры?

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

Я пробовал различные формы из следующих результатов без успеха.

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

Этот код дает мне ошибку компиляции

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`
4b9b3361

Ответ 1

Ваш тип итератора - Iterator<Item = Self::Item>, но Iterator - это особенность. Черты реализуются структурами, они не существуют сами по себе. Вы также можете иметь объект ссылочного признака (&Iterator), объект в штучной упаковке (Box<Iterator>) или реализацию анонимного признака (impl Iterator), каждый из которых имеет известные размеры.

Вместо этого мы создаем 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,
        }
    }
}

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, вам также необходимо реализовать итератор, который его не использует:

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:

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

Ответ 2

Во-первых, IntoIter должен указывать на реальный struct, а не на trait, чтобы Rust мог передавать значение вокруг (что означает Sized). В случае массивов into_iter возвращает std:: slice:: Iter struct.

Во-вторых, типичный массив [1, 2, 3] не выделяется в куче. Фактически, компилятору разрешено полностью исключить выделение, вместо этого указывая на предварительно скомпилированный массив. Возможность перебора массивов без их копирования в любом месте, я думаю, причина, по которой реализация IntoIterator для массивов не перемещает массив нигде, как это делают другие реализации IntoIterator. Вместо этого он, похоже, ссылается на существующий массив. Вы можете видеть из свою подпись

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, которая действительно перемещает данные в итератор, и поэтому вы можете использовать его.


P.S. Чтобы сделать это быстро и просто, верните массив вместо итератора (playpen):

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

Таким образом, массив сначала перемещается во внешнюю область видимости и затем ссылается на итератор внешней области:

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