Skip to main content

ps_range/
start.rs

1use num_traits::Zero;
2
3/// A range with a defined inclusive lower bound.
4///
5/// This is the shared base of the crate's extension traits. The
6/// implementations for
7/// [`std::ops::RangeTo`], [`std::ops::RangeToInclusive`],
8/// [`std::ops::RangeFull`], and [`None`] anchor the start at
9/// [`Idx::zero()`](Zero::zero); for a signed index type the negative portion
10/// is not represented, so `(..-5)` reports itself empty.
11///
12/// For [`std::ops::RangeInclusive`], the inherent `start` method shadows this
13/// one; call it as `RangeStart::start(&range)`.
14///
15/// # Examples
16///
17/// ```
18/// use ps_range::RangeStart;
19///
20/// assert_eq!((2..8).start(), 2);
21/// assert_eq!((..8).start(), 0);
22/// assert_eq!(RangeStart::start(&(2..=8)), 2);
23/// ```
24pub trait RangeStart<Idx = usize> {
25    /// Returns the inclusive lower bound of the range.
26    #[must_use]
27    fn start(&self) -> Idx;
28}
29
30impl<Idx: Clone> RangeStart<Idx> for std::ops::Range<Idx> {
31    #[inline]
32    fn start(&self) -> Idx {
33        self.start.clone()
34    }
35}
36
37impl<Idx: Clone> RangeStart<Idx> for std::ops::RangeFrom<Idx> {
38    #[inline]
39    fn start(&self) -> Idx {
40        self.start.clone()
41    }
42}
43
44impl<Idx: Zero> RangeStart<Idx> for std::ops::RangeFull {
45    #[inline]
46    fn start(&self) -> Idx {
47        Idx::zero()
48    }
49}
50
51impl<Idx: Clone> RangeStart<Idx> for std::ops::RangeInclusive<Idx> {
52    #[inline]
53    fn start(&self) -> Idx {
54        self.start().clone()
55    }
56}
57
58impl<Idx: Zero> RangeStart<Idx> for std::ops::RangeTo<Idx> {
59    #[inline]
60    fn start(&self) -> Idx {
61        Idx::zero()
62    }
63}
64
65impl<Idx: Zero> RangeStart<Idx> for std::ops::RangeToInclusive<Idx> {
66    #[inline]
67    fn start(&self) -> Idx {
68        Idx::zero()
69    }
70}
71
72impl<Idx, T> RangeStart<Idx> for Option<T>
73where
74    Idx: Zero,
75    T: RangeStart<Idx>,
76{
77    #[inline]
78    fn start(&self) -> Idx {
79        self.as_ref().map_or_else(Idx::zero, RangeStart::start)
80    }
81}
82
83impl<Idx, T: RangeStart<Idx>> RangeStart<Idx> for &T {
84    #[inline]
85    fn start(&self) -> Idx {
86        (*self).start()
87    }
88}
89
90impl<Idx, T: RangeStart<Idx>> RangeStart<Idx> for &mut T {
91    #[inline]
92    fn start(&self) -> Idx {
93        (**self).start()
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::RangeStart;
100
101    #[test]
102    fn stored_starts_are_reported() {
103        assert_eq!((2usize..8).start(), 2);
104        assert_eq!((2usize..).start(), 2);
105        assert_eq!(RangeStart::start(&(2usize..=8)), 2);
106    }
107
108    #[test]
109    fn end_only_ranges_anchor_at_zero() {
110        assert_eq!(RangeStart::<i32>::start(&(..)), 0);
111        assert_eq!((..8i32).start(), 0);
112        assert_eq!((..=-1i32).start(), 0);
113    }
114
115    #[test]
116    fn none_anchors_at_zero() {
117        assert_eq!(None::<std::ops::Range<usize>>.start(), 0);
118        assert_eq!(Some(2usize..8).start(), 2);
119    }
120
121    #[test]
122    fn references_forward() {
123        fn start_of<Idx, R: RangeStart<Idx>>(range: R) -> Idx {
124            range.start()
125        }
126
127        let mut range = 2usize..8;
128
129        assert_eq!(start_of(&range), 2);
130        assert_eq!(start_of(&mut range), 2);
131    }
132}