Impl IntoIterator and Iterator for a reference

Hi everybody,

I successfully implemented IntoIterator for a few custom types but now I need to implement it for a refence and I couldn't find any examples how this is usually done. All examples I found are consuming the custom type. So I came up with my own idea that works. Could you please look at the code of IntoIterBorrow and tell me if this is the way how it's usually done?

Thank you.

Playground

struct IntoIterBorrow<'a> {
    items: &'a Vec<FooBar>,
    index: usize,
}
impl<'a> Iterator for IntoIterBorrow<'a> {
    type Item = &'a FooBar;

    fn next(&mut self) -> Option<Self::Item> {
        self.items.get(self.index).map(|item| {
            self.index += 1;
            item
        })
    }
}

struct IntoIterMoved {
    items: Vec<FooBar>,
}
impl Iterator for IntoIterMoved {
    type Item = FooBar;

    fn next(&mut self) -> Option<Self::Item> {
        self.items.pop()
    }
}

#[derive(Debug)]
struct FooBar {
    name: String,
    number: i32,
}
impl FooBar {
    fn new(name: &str, number: i32) -> Self {
        Self {
            name: name.to_owned(),
            number,
        }
    }
    fn get_name(&self) -> String {
        format!("{}{}", self.name, self.number)
    }
}

#[derive(Debug)]
struct MyStruct {
    items: Vec<FooBar>,
}
impl MyStruct {
    fn new(items: Vec<FooBar>) -> Self {
        Self { items }
    }
}
impl IntoIterator for MyStruct {
    type Item = FooBar;
    type IntoIter = IntoIterMoved;

    fn into_iter(self) -> Self::IntoIter {
        let mut items = self.items;
        items.reverse();
        IntoIterMoved { items }
    }
}
impl<'a> IntoIterator for &'a MyStruct {
    type Item = &'a FooBar;
    type IntoIter = IntoIterBorrow<'a>;

    fn into_iter(self) -> Self::IntoIter {
        IntoIterBorrow {
            items: &self.items,
            index: 0,
        }
    }
}

fn main() {
    let x = MyStruct::new(vec![
        FooBar::new("abc", 1),
        FooBar::new("def", 2),
        FooBar::new("ghi", 3),
    ]);
    let y: Vec<_> = x.into_iter().map(|x| x.get_name()).collect();
    println!("{:?}", y);
    // println!("{:?}", x); // as expected - error

    let x = MyStruct::new(vec![
        FooBar::new("abc", 1),
        FooBar::new("def", 2),
        FooBar::new("ghi", 3),
    ]);
    let y: Vec<_> = (&x).into_iter().map(|x| x.get_name()).collect();
    println!("{:?}", y);
    println!("{:?}", x); // as expected - ok
}

Your code is fine (see impl Iterator for &[T], which eventually[1] calls std::slice::Iter::new()).

If these iterators are just for learning purposes it's okay, but the simpler way to do it would be to reuse the Vec and slice iterators:

use std::{vec, slice};

// struct FooBar omitted

#[derive(Debug)]
struct MyStruct {
    items: Vec<FooBar>,
}
impl MyStruct {
    fn new(items: Vec<FooBar>) -> Self {
        Self { items }
    }
    
    // By convention, a .iter() method is provided,
    // equal to (&MyStruct).into_iter()
    fn iter(&self) -> slice::Iter<'_, FooBar> {
        self.items.iter()
    }
}

impl IntoIterator for MyStruct {
    type Item = FooBar;
    type IntoIter = vec::IntoIter<FooBar>;

    fn into_iter(self) -> Self::IntoIter {
        self.items.into_iter()
    }
}
impl<'a> IntoIterator for &'a MyStruct {
    type Item = &'a FooBar;
    type IntoIter = slice::Iter<'a, FooBar>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}

// fn main omitted, unchanged

Playground


  1. through [T]::iter ↩ī¸Ž

4 Likes

Thank you very much @cod10129 !

It is both for learning purposes (I learned that I need to mutate something else than items in the IntoIterBorrow - in my case the index) but also for actual code.

The reason I want to implement IntoIterator trait instead of just fn iter() is that I'll have MyStructA and MyStructB and I want to iterate them in a generic function (T: IntoIterator<Item = FooBar>) so iter() method is not helpful.

EDIT:
Looking at your code again, I get it now - I don't need to type IntoIterMoved and IntoIterBorrow - I can just re-use vec::IntoIter<T> and slice::Iter<T> that most likely internally work the same as my IntoIterMoved and IntoIterBorrow. Thank you.

If your problem is solved, you can mark their answer as the solution. That helps future readers, and lets people know that you no longer need help.

3 Likes