Repository: rust-itertools/itertools
Pull request #932 (2024-05-08 21:51:00)

`assert_equal`: fix `clippy::default_numeric_fallback`
Type of `i` defaults to `i32` when `usize` should be used.
It's only a problem if this function is called with two iterators longer than `i32::MAX`. It seems unlikely to me but who knows?
`i32` should not be the default integer type in the library. `usize` would have more sense so **_I add the lint as warning_** (for the library only, it's okay in tests and benchmarks).

See lint doc: https://rust-lang.github.io/rust-clippy/master/index.html#/default_numeric_fallback

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/rust-itertools/itertools/pull/932?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.50%. Comparing base [(`6814180`)](https://app.codecov.io/gh/rust-itertools/itertools/commit/68141809aabec2816d3250820c8d6608f3b2293f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) to head [(`6adeee1`)](https://app.codecov.io/gh/rust-itertools/itertools/pull/932?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> Report is 71 commits behind head on master.


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #932      +/-   ##
==========================================
+ Coverage   94.38%   94.50%   +0.11%     
==========================================
  Files          48       48              
  Lines        6665     6949     +284     
==========================================
+ Hits         6291     6567     +276     
- Misses        374      382       +8     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/rust-itertools/itertools/pull/932?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/src/iter_index.rs b/src/iter_index.rs
new file mode 100644
index 0000000..aadaa72
--- /dev/null
+++ b/src/iter_index.rs
@@ -0,0 +1,116 @@
+use core::iter::{Skip, Take};
+use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive};
+
+#[cfg(doc)]
+use crate::Itertools;
+
+mod private_iter_index {
+    use core::ops;
+
+    pub trait Sealed {}
+
+    impl Sealed for ops::Range<usize> {}
+    impl Sealed for ops::RangeInclusive<usize> {}
+    impl Sealed for ops::RangeTo<usize> {}
+    impl Sealed for ops::RangeToInclusive<usize> {}
+    impl Sealed for ops::RangeFrom<usize> {}
+    impl Sealed for ops::RangeFull {}
+}
+
+/// Used by [`Itertools::get`] to know which iterator
+/// to turn different ranges into.
+pub trait IteratorIndex<I>: private_iter_index::Sealed
+where
+    I: Iterator,
+{
+    /// The type returned for this type of index.
+    type Output: Iterator<Item = I::Item>;
+
+    /// Returns an adapted iterator for the current index.
+    ///
+    /// Prefer calling [`Itertools::get`] instead
+    /// of calling this directly.
+    fn index(self, from: I) -> Self::Output;
+}
+
+impl<I> IteratorIndex<I> for Range<usize>
+where
+    I: Iterator,
+{
+    type Output = Skip<Take<I>>;
+
+    fn index(self, iter: I) -> Self::Output {
+        iter.take(self.end).skip(self.start)
+    }
+}
+
+impl<I> IteratorIndex<I> for RangeInclusive<usize>
+where
+    I: Iterator,
+{
+    type Output = Take<Skip<I>>;
+
+    fn index(self, iter: I) -> Self::Output {
+        // end - start + 1 without overflowing if possible
+        let length = if *self.end() == usize::MAX {
+            assert_ne!(*self.start(), 0);
+            self.end() - self.start() + 1
+        } else {
+            (self.end() + 1).saturating_sub(*self.start())
+        };
+        iter.skip(*self.start()).take(length)
+    }
+}
+
+impl<I> IteratorIndex<I> for RangeTo<usize>
+where
+    I: Iterator,
+{
+    type Output = Take<I>;
+
+    fn index(self, iter: I) -> Self::Output {
+        iter.take(self.end)
+    }
+}
+
+impl<I> IteratorIndex<I> for RangeToInclusive<usize>
+where
+    I: Iterator,
+{
+    type Output = Take<I>;
+
+    fn index(self, iter: I) -> Self::Output {
+        assert_ne!(self.end, usize::MAX);
+        iter.take(self.end + 1)
+    }
+}
+
+impl<I> IteratorIndex<I> for RangeFrom<usize>
+where
+    I: Iterator,
+{
+    type Output = Skip<I>;
+
+    fn index(self, iter: I) -> Self::Output {
+        iter.skip(self.start)
+    }
+}
+
+impl<I> IteratorIndex<I> for RangeFull
+where
+    I: Iterator,
+{
+    type Output = I;
+
+    fn index(self, iter: I) -> Self::Output {
+        iter
+    }
+}
+
+pub fn get<I, R>(iter: I, index: R) -> R::Output
+where
+    I: IntoIterator,
+    R: IteratorIndex<I::IntoIter>,
+{
+    index.index(iter.into_iter())
+}
diff --git a/src/lib.rs b/src/lib.rs
index 931a28f..f4de79c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-#![warn(missing_docs)]
+#![warn(missing_docs, clippy::default_numeric_fallback)]
 #![crate_name = "itertools"]
 #![cfg_attr(not(feature = "use_std"), no_std)]
 
@@ -146,6 +146,7 @@ pub mod structs {
 
 /// Traits helpful for using certain `Itertools` methods in generic contexts.
 pub mod traits {
+    pub use crate::iter_index::IteratorIndex;
     pub use crate::tuple_impl::HomogeneousTuple;
 }
 
@@ -194,6 +195,7 @@ mod groupbylazy;
 #[cfg(feature = "use_std")]
 mod grouping_map;
 mod intersperse;
+mod iter_index;
 #[cfg(feature = "use_alloc")]
 mod k_smallest;
 #[cfg(feature = "use_alloc")]
@@ -504,6 +506,56 @@ pub trait Itertools: Iterator {
         intersperse::intersperse_with(self, element)
     }
 
+    /// Returns an iterator over a subsection of the iterator.
+    ///
+    /// Works similarly to [`slice::get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get).
+    ///
+    /// **Panics** for ranges `..=usize::MAX` and `0..=usize::MAX`.
+    ///
+    /// It's a generalisation of [`Iterator::take`] and [`Iterator::skip`],
+    /// and uses these under the hood.
+    /// Therefore, the resulting iterator is:
+    /// - [`ExactSizeIterator`] if the adapted iterator is [`ExactSizeIterator`].
+    /// - [`DoubleEndedIterator`] if the adapted iterator is [`DoubleEndedIterator`] and [`ExactSizeIterator`].
+    ///
+    /// # Unspecified Behavior
+    /// The result of indexing with an exhausted [`core::ops::RangeInclusive`] is unspecified.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// let vec = vec![3, 1, 4, 1, 5];
+    ///
+    /// let mut range: Vec<_> =
+    ///         vec.iter().get(1..=3).copied().collect();
+    /// assert_eq!(&range, &[1, 4, 1]);
+    ///
+    /// // It works with other types of ranges, too
+    /// range = vec.iter().get(..2).copied().collect();
+    /// assert_eq!(&range, &[3, 1]);
+    ///
+    /// range = vec.iter().get(0..1).copied().collect();
+    /// assert_eq!(&range, &[3]);
+    ///
+    /// range = vec.iter().get(2..).copied().collect();
+    /// assert_eq!(&range, &[4, 1, 5]);
+    ///
+    /// range = vec.iter().get(..=2).copied().collect();
+    /// assert_eq!(&range, &[3, 1, 4]);
+    ///
+    /// range = vec.iter().get(..).copied().collect();
+    /// assert_eq!(range, vec);
+    /// ```
+    fn get<R>(self, index: R) -> R::Output
+    where
+        Self: Sized,
+        R: traits::IteratorIndex<Self>,
+    {
+        iter_index::get(self, index)
+    }
+
     /// Create an iterator which iterates over both this and the specified
     /// iterator simultaneously, yielding pairs of two optional elements.
     ///
@@ -4225,7 +4277,7 @@ where
 {
     let mut ia = a.into_iter();
     let mut ib = b.into_iter();
-    let mut i = 0;
+    let mut i: usize = 0;
     loop {
         match (ia.next(), ib.next()) {
             (None, None) => return,


Test patch:
diff --git a/tests/laziness.rs b/tests/laziness.rs
index 2ce29cb..f936dec 100644
--- a/tests/laziness.rs
+++ b/tests/laziness.rs
@@ -63,6 +63,14 @@ must_use_tests! {
     intersperse_with {
         let _ = Panicking.intersperse_with(|| 0);
     }
+    get {
+        let _ = Panicking.get(1..4);
+        let _ = Panicking.get(1..=4);
+        let _ = Panicking.get(1..);
+        let _ = Panicking.get(..4);
+        let _ = Panicking.get(..=4);
+        let _ = Panicking.get(..);
+    }
     zip_longest {
         let _ = Panicking.zip_longest(Panicking);
     }
diff --git a/tests/test_core.rs b/tests/test_core.rs
index b5826ef..32af246 100644
--- a/tests/test_core.rs
+++ b/tests/test_core.rs
@@ -18,6 +18,41 @@ use crate::it::Itertools;
 use core::iter;
 use itertools as it;
 
+#[allow(dead_code)]
+fn get_esi_then_esi<I: ExactSizeIterator + Clone>(it: I) {
+    fn is_esi(_: impl ExactSizeIterator) {}
+    is_esi(it.clone().get(1..4));
+    is_esi(it.clone().get(1..=4));
+    is_esi(it.clone().get(1..));
+    is_esi(it.clone().get(..4));
+    is_esi(it.clone().get(..=4));
+    is_esi(it.get(..));
+}
+
+#[allow(dead_code)]
+fn get_dei_esi_then_dei_esi<I: DoubleEndedIterator + ExactSizeIterator + Clone>(it: I) {
+    fn is_dei_esi(_: impl DoubleEndedIterator + ExactSizeIterator) {}
+    is_dei_esi(it.clone().get(1..4));
+    is_dei_esi(it.clone().get(1..=4));
+    is_dei_esi(it.clone().get(1..));
+    is_dei_esi(it.clone().get(..4));
+    is_dei_esi(it.clone().get(..=4));
+    is_dei_esi(it.get(..));
+}
+
+#[test]
+fn get_1_max() {
+    let mut it = (0..5).get(1..=usize::MAX);
+    assert_eq!(it.next(), Some(1));
+    assert_eq!(it.next_back(), Some(4));
+}
+
+#[test]
+#[should_panic]
+fn get_full_range_inclusive() {
+    let _it = (0..5).get(0..=usize::MAX);
+}
+
 #[test]
 fn product0() {
     let mut prod = iproduct!();