Repository: rust-itertools/itertools (language: rust)

Issue:
Laziness in tuple_windows
I was bit surprised by that `Itertools::tuple_windows` is not somewhat lazy and has side effect.
It is consuming the first tuple when calling `Itertools::tuple_windows` .

https://doc.rust-lang.org/nightly/core/iter/index.html#laziness says that adapters in `Iterator` is lazy and 
the `TupleWindows` has `#[must_use = "iterator adaptors are lazy and do nothing unless consumed]` since #227

So the question is
- Is this intended behavior?  
- Should (all) adapters in `Itertools` be lazy?
- Should the laziness be documented?
  


Discussion / hints:

Fix patch:
diff --git a/src/size_hint.rs b/src/size_hint.rs
index d592ca925..857e0c4c6 100644
--- a/src/size_hint.rs
+++ b/src/size_hint.rs
@@ -29,7 +29,6 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint {
 }
 
 /// Subtract `x` correctly from a `SizeHint`.
-#[cfg(feature = "use_alloc")]
 #[inline]
 pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint {
     let (mut low, mut hi) = sh;
diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs
index c0b39d79d..688df448e 100644
--- a/src/tuple_impl.rs
+++ b/src/tuple_impl.rs
@@ -5,6 +5,8 @@ use std::iter::Fuse;
 use std::iter::FusedIterator;
 use std::marker::PhantomData;
 
+use crate::size_hint;
+
 // `HomogeneousTuple` is a public facade for `TupleCollect`, allowing
 // tuple-related methods to be used by clients in generic contexts, while
 // hiding the implementation details of `TupleCollect`.
@@ -172,25 +174,13 @@ where
 }
 
 /// Create a new tuple windows iterator.
-pub fn tuple_windows<I, T>(mut iter: I) -> TupleWindows<I, T>
+pub fn tuple_windows<I, T>(iter: I) -> TupleWindows<I, T>
 where
     I: Iterator<Item = T::Item>,
     T: HomogeneousTuple,
     T::Item: Clone,
 {
-    use std::iter::once;
-
-    let mut last = None;
-    if T::num_items() != 1 {
-        // put in a duplicate item in front of the tuple; this simplifies
-        // .next() function.
-        if let Some(item) = iter.next() {
-            let iter = once(item.clone()).chain(once(item)).chain(&mut iter);
-            last = T::collect_from_iter_no_buf(iter);
-        }
-    }
-
-    TupleWindows { iter, last }
+    TupleWindows { last: None, iter }
 }
 
 impl<I, T> Iterator for TupleWindows<I, T>
@@ -205,19 +195,29 @@ where
         if T::num_items() == 1 {
             return T::collect_from_iter_no_buf(&mut self.iter);
         }
-        if let Some(ref mut last) = self.last {
-            if let Some(new) = self.iter.next() {
+        if let Some(new) = self.iter.next() {
+            if let Some(ref mut last) = self.last {
                 last.left_shift_push(new);
-                return Some(last.clone());
+                Some(last.clone())
+            } else {
+                use std::iter::once;
+                let iter = once(new).chain(&mut self.iter);
+                self.last = T::collect_from_iter_no_buf(iter);
+                self.last.clone()
             }
+        } else {
+            None
         }
-        None
     }
 
     fn size_hint(&self) -> (usize, Option<usize>) {
-        // At definition, `T::num_items() - 1` are collected
-        // so each remaining item in `iter` will lead to an item.
-        self.iter.size_hint()
+        let mut sh = self.iter.size_hint();
+        // Adjust the size hint at the beginning
+        // OR when `num_items == 1` (but it does not change the size hint).
+        if self.last.is_none() {
+            sh = size_hint::sub_scalar(sh, T::num_items() - 1);
+        }
+        sh
     }
 }
 


Test patch: