Why there is no windows for iterators?

I found that there is no .windows() for iterators, which is sound strange to me. I can do windows() over a slice or a Vec, but I can't do it over an iterator. I'm not sure I can write my own with ease, but (without trying) I suspect it should be easy. All it needs is to implement a get function and store some circular fixed size buffer in Windows structure.

Are there some real reasons for not having windows for iterator? Some code would become a bliss it it was there...

The trouble with this is that the value returned from next would need to borrow from the iterator itself, which is not allowed by the signature fn next(&mut self) -> Option<Self::Item>. A "lending iterator" using a generic associated type could have such a method, though.

2 Likes

There's tuple_windows in itertools, which is probably the standard de-facto for the cases around iteration which aren't polished enough to fulfill the standard library stability guarantee.

3 Likes

I don't get it.

Some crazy pseudo-code for windows(2), for returning first two elements. I skip whole "rolling" part here, but the function signature does not prevent me from calling inner iterator few times:

impl Window.. {
    fn next (&mut self) -> Option<Item>{
       self.one = self.inner_iter.next();
       self.two = self.inner_iter.next();
       if self.one.is_some() && self.two.is_some(){
           return Some([self.one.unwrap(), self.two.unwrap()])
       }
}

This works because you're returning an owned, newly-constructed array from next, not a reference to an internally stored buffer. As soon as you try to do the second thing you will get an error.

The slice type [T] gets around this because the items are already stored contiguously in memory, so it can return references to that memory region instead of a buffer owned by the iterator. For general iterators there's no guarantee that the items are stored contiguously somewhere.

8 Likes

The Iterator interface guarantees that this is always valid:

let a = iter.next().unwrap();
let b = iter.next();
drop(iter);
use_both(a,b);

If you allowed windows, it would allow mutable aliasing:

let mut iter = x.iter_mut().windows(2);
let a = iter.next()?;
let b = iter.next()?;
drop(iter);
/// a[1] aliases b[0], nasal demons release

and/or wouldn't match lifetimes that Iterator promises to be valid, failing compilation or breaking the safety of the borrow checker.

7 Likes

See this for a bunch of conversation about why windows is awkward, but a proposed API to offer instead:

(Add `Iterator::map_windows` by LukasKalbertodt · Pull Request #82413 · rust-lang/rust · GitHub)

EDIT 2023 looks like it landed in nightly, see

That makes it chunks, not windows, which makes a huge difference. chunks only needs to give access to each item once, which means that no-copy move-only semantics works great. windows is much harder -- note that slices have chunks and chunks_mut, but only windows no windows_mut.

7 Likes

Temporarily opting-in to shared mutation lets you use windows and similar slice iterators in a mutable (albeit more limited) fashion.

1 Like

Would the Peekable trait work for this in some limited (.windows(2)) cases?

I don't see much of a benefit. next() still only returns one element and you still can't use for like you can with windows.

let mut iter = v.iter_mut().peekable();
while let Some(one) = iter.next() {
    if let Some(two) = iter.peek_mut() {
       // Do stuff with `one` and `*two`
    }
}
// vs.
let mut iter = v.iter_mut();
if let Some(mut one) = iter.next() {
    while let Some(two) = iter.next() {
        // Do stuff with `one` and `two`, then
        one = two
    }
}

(And a potential downside that the peek has a limited lifetime ala lending iterators.)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.