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

Issue:
Laziness of our iterators
Similar to #601 but for all our iterators and iterator adaptors.
Laziness reference: https://doc.rust-lang.org/std/iter/index.html#laziness

And I think each iterator struct should have the related `must_use` attribute:
- Adaptors: `#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]`
- Non-adaptors: `#[must_use = "iterators are lazy and do nothing unless consumed"]`

Load some items might be unavoidable in some case(?) but surely not all. TODO:
- [x] #794
- [x] #793 `permutations(n)` consumes `n` items at definition (so not lazy when `n >= 1`).
- [x] #795 `combinations(n)` consumes `n` items at definition (so not lazy when `n >= 1`).
- [x] #797 `intersperse[_with]` both consume 1 item at definition.
- [x] #800 `a.cartesian_product(b)` consumes 1 item of `a` at definition.
    Therefore `iproduct!(i_1, i_2, i_n)` consumes 1 item of the first `n - 1` iterators at definition.
- [x] #801 `coalesce`, `dedup[_by][_with_count]` all consume 1 item at definition.
- [x] #792
- [x] #767 mention laziness for new iterator adaptors and where to test it.
- [ ] `tuple_combinations::<T>` consume 1 item per nested iterator at definition (`"T::len()" - 1` items so not lazy for `(_, _, ...)`).
- [ ] `kmerge[_by]` collect all items and consume 1 item of each iterator at definition.
- [x] ~Maybe update the documentation of all "eager" adaptors to say that there are "eager":~
    ~`dropping dropping_back sorted_unstable sorted_unstable_by sorted_unstable_by_key sorted sorted_by sorted_by_key sorted_by_cached_key k_smallest k_smallest_by k_smallest_by_key k_largest k_largest_by k_largest_by_key tail` (so `dropping[_back]` and methods returning `Vec[Deque]IntoIter`).~
    It's already good enough, it says it's "eager", or that "it consumes the entire iterator", or that "if collected to a `Vec[Deque]`, is converted without any extra copying or allocation cost".


Discussion / hints:
About `TupleCombinations` and `KMergeBy`, the only way I see to make them lazy is to delay the initialization step with a `LazyInit` type (for e.g. based on `Into` conversion which is already done for the `TupleCombinations` part).

    // `Option` (or a third variant) is a necessary implementation detail to take ownership of `U`.
    // `Uninit(None)` (or the 3rd variant) would be unreachable.
    pub(crate) enum LazyInit<U, I> {
        Uninit(Option<U>),
        Init(I),
    }
    impl<U, I> LazyInit<U, I> {                               // useful later for
        pub fn new(uninit: U) -> Self;                        // at definition
        pub fn get(&self) -> Result<&I, &U>;                  // size_hint
        pub fn get_mut(&mut self) -> &mut I where U: Into<I>; // next, ...
        pub fn take_init(self) -> I where U: Into<I>;         // fold
        // eventually
        pub fn take_uninit(self) -> Result<U, I>;             // count
    }

The most important method is `get_mut` which handle initialization once and simply return the mutable reference of `I` otherwise.

I worry it might result in a slower iterator.

@jswrenn @phimuemue What do you think about such pattern/type?

Fix patch:
diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs
index 22f7aa363..52e36c48b 100644
--- a/src/adaptors/mod.rs
+++ b/src/adaptors/mod.rs
@@ -612,7 +612,7 @@ where
 /// See [`.tuple_combinations()`](crate::Itertools::tuple_combinations) for more
 /// information.
 #[derive(Clone, Debug)]
-#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
+#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"]
 pub struct TupleCombinations<I, T>
 where
     I: Iterator,
diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs
index 4660caf07..0be3840a1 100644
--- a/src/kmerge_impl.rs
+++ b/src/kmerge_impl.rs
@@ -153,7 +153,7 @@ where
 ///
 /// See [`.kmerge_by()`](crate::Itertools::kmerge_by) for more
 /// information.
-#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
+#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"]
 pub struct KMergeBy<I, F>
 where
     I: Iterator,


Test patch:
diff --git a/tests/laziness.rs b/tests/laziness.rs
index f936decde..c559d33ad 100644
--- a/tests/laziness.rs
+++ b/tests/laziness.rs
@@ -143,11 +143,11 @@ must_use_tests! {
         let _ = Panicking.merge_join_by(Panicking, |_, _| true);
         let _ = Panicking.merge_join_by(Panicking, Ord::cmp);
     }
-    #[ignore]
+    #[should_panic]
     kmerge {
         let _ = Panicking.map(|_| Panicking).kmerge();
     }
-    #[ignore]
+    #[should_panic]
     kmerge_by {
         let _ = Panicking.map(|_| Panicking).kmerge_by(|_, _| true);
     }
@@ -196,10 +196,15 @@ must_use_tests! {
     while_some {
         let _ = Panicking.map(Some).while_some();
     }
-    #[ignore]
-    tuple_combinations {
+    tuple_combinations1 {
         let _ = Panicking.tuple_combinations::<(_,)>();
+    }
+    #[should_panic]
+    tuple_combinations2 {
         let _ = Panicking.tuple_combinations::<(_, _)>();
+    }
+    #[should_panic]
+    tuple_combinations3 {
         let _ = Panicking.tuple_combinations::<(_, _, _)>();
     }
     combinations {