#Interleaving iterator code review

23 messages · Page 1 of 1 (latest)

candid imp
#

I wrote immutable and mutable variants of an iterator that interleaves N iterators until one of them runs out. I had to avoid using allocations since this is used in DSP code, so I used an array of MaybeUninit objects for storing the iterators. Can anyone spot anything funny here?

Here's the gist as it was too long for Discord: https://gist.github.com/ilmai/bd2c92920a4b3304a5040a61723c1fc5

snow cradle
#

Do you have to take them as an iterator of iterators? If you take it as an array, you can use a const generic for the array length

candid imp
#

Nope, the number of sub-iterators isn't known at compile time

snow cradle
#

Then there's the arrayvec crate, which lets you avoid MaybeUninit.

#

And I don't think you need a mut variant.

candid imp
#

Ah you're correct, that was a leftover from earlier before I made this generic...

snow cradle
#

I think you can replace this

if self.next_iterator_index >= self.iterators.len() {
    return None
}
```with 
```rs
if self.iterator_count == 0 {
    return None
}
candid imp
#

Ah yes, good catch

amber mortar
#

you can use option rather than maybeuninit, and use no unsafe

#

also, this is a big problem:

#
fn main() {
    let v = vec![1_u8, 2, 3].into_iter();
    let w = vec![4, 5, 6].into_iter();
    InterleaveIterator::new([v, w]);
}```
#

|| it leaks memory ||

candid imp
#

Ah right, objects inside MaybeUninit are implicitly ManuallyDrop?

amber mortar
#

?play ```rust
pub fn foo<T, const N: usize>() -> [Option<T>; N] {
[0; N].map(|_| None)
}

loud dirgeBOT
#
     Running `target/debug/playground`
candid imp
#

Ah wait I can use Default

amber mortar
candid imp
#

Yep good catch.

snow cradle
#

Here's what I figure:

use arrayvec::ArrayVec;

const MAX_ITERATORS: usize = 4;

pub struct InterleaveIterator<T, I: Iterator<Item = T>> {
    iterators: ArrayVec<I, MAX_ITERATORS>,
    next_index: usize,
}

impl<T, I> InterleaveIterator<T, I>
where
    I: Iterator<Item = T>
{
    pub fn new<II: IntoIterator<Item = I>>(iterators: II) -> InterleaveIterator<T, I> {
        let iterators = iterators.into_iter().collect();
        InterleaveIterator {
            iterators,
            next_index: 0
        }
    }
}

impl<T, I: Iterator<Item = T>> Iterator for InterleaveIterator<T, I> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
        if self.iterators.is_empty() {
            return None
        }
        
        let result = self.iterators[self.next_index].next();
        
        self.next_index += 1;
        self.next_index %= self.iterators.len();

        result
    }
}
amber mortar
#

of course the danger with that is that you might end up dropping things twice which would be incredibly bad

#

(if you mess up)

snow cradle
#

And then of course you can reimplement stuff like skip and nth (or advance_by on nightly) so that it stays efficient when the underlying iterators have efficient implementations.