Iter macro for owned iterators without heap allocation

Something I've run into a few times is trying to make an owned iterator of a few given values. The issue is that [T; N] does not implement IntoIterator, so the resulting iterator is a borrow. To combat this I came up with this macro:

macro_rules! iter {
    [$item:expr; $count:expr] => {
        std::iter::repeat($item).take($count)
    };
    [$($item:expr),*] => {{
        const LEN: usize = [$(zero!($item)),*].len();
        struct Iter<T> {
            index: usize,
            values: std::mem::ManuallyDrop<[T; LEN]>,
        }
        impl<T> Iterator for Iter<T> {
            type Item = T;
            fn next(&mut self) -> Option<T> {
                if self.index < LEN {
                    let val = unsafe { std::ptr::read(&self.values[self.index]) };
                    self.index += 1;
                    Some(val)
                } else {
                    None
                }
            }
        }
        
        impl<T> Drop for Iter<T> {
            fn drop(&mut self) {
                for ptr in &mut self.values[self.index..] {
                    unsafe { std::ptr::drop_in_place(ptr); }
                }
            }
        }
        Iter {
            index: 0,
            values: std::mem::ManuallyDrop::new([$($item),*]),
        }
    }};
    [$($item:expr,)*] => { iter!($($item),*) };
}

playground

What do you think? I looked around and found some old implementations of this, e.g. here and literator, but they're more hacky and limited in length.

Also I usually look up how to count here, but it seems it is much easier now that slice::len is const.

In drop you can just do,

::std::ptr::drop_in_place(&mut self.values[self.index..]);

You can also remove a bounds check from the index

if self.index < LEN {
    let val = unsafe {
        self.values.as_ptr()
            .add(self.index)
            .read()
    };
    self.index += 1;
    Some(val)
} else {
    None
}
1 Like

If you're using this a lot and/or want to avoid an explosion of bespoke iterators, you could build your macro around the arrayvec crate. Something like ArrayVec::from([$($item),*]).into_iter()

3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.