I'm trying to implement an iterator adapter that is similar to C++ ranges' std::views::split
, but I got lost in the Rust type system and lifetimes.
More specifically, I want to have a function iter_split
that takes an iterator and a separator and produces a series of iterators such that a new iterator starts when the original iterator outputs the separator. It's better to illustrate it by the following example:
let v = vec![1, 2, 3, 0, 1, 2, 0, 4, 5, 0];
for inner_iter in iter_split(v.iter(), &0) {
inner_iter.for_each(|x| print!("{} ", x));
println!();
}
The expected output is
1 2 3
1 2
4 5
I tried to define it as follows (it's more like a pseudocode at this point):
struct Split<I, T> {
iter: I,
sep: T,
}
impl<I, T> Split<I, T> {
fn new(iter: I, sep: T) -> Split<I, T> {
Split { iter, sep }
}
}
impl<I, T> Iterator for Split<I, T>
where
I: Iterator<Item = T>,
T: std::cmp::PartialEq,
{
type Item = /* ??? */;
fn next(&mut self) -> Option<Self::Item> {
let inner_iter = self.iter.by_ref().take_while(|x| x != self.sep).peekable();
match inner_iter.peek() {
None => None,
Some(_) => Some(inner_iter),
}
}
}
fn iter_split<'a, I, T>(iter: I, sep: T) -> Split<I, T>
where
I: Iterator<Item = T>,
T: std::cmp::PartialEq,
{
Split::new(iter, sep)
}
This approach has several issues:
- I don't know what to write as the type for
Item
: it should be something likePeekable<TakeWhile<...>>
, but since I pass the iterator (and the separator, technically) by reference, I should also take care of their lifetimes, and I'm not used to them yet. - I can't use a closure since I won't be able to return it just like it. This, however, can at least be alleviated by defining another adaptor that behaves like
TakeWhile
but instead of checking a predicate, compares the output with the separator.
It seems that I probably can solve some of them using Box
, but I'm not sure. Can I do it without boxes?