Skip to main content

ps_range/
range_ext.rs

1use num_traits::{CheckedAdd, One, SaturatingSub};
2
3use crate::{Range, RangeEnd};
4
5/// A range bounded on both ends.
6///
7/// The upper bound is exposed in both of its forms:
8/// [`end_exclusive`](RangeExt::end_exclusive) returns [`None`] exactly when
9/// the exclusive bound would exceed the maximum index value, that is, when
10/// the range ends inclusively at the maximum, and
11/// [`end_inclusive`](RangeExt::end_inclusive) is meaningful only for a
12/// non-empty range, so `(0..0).end_inclusive()` is `0`. The clamping
13/// operations consult only [`end_exclusive`](RangeExt::end_exclusive) and
14/// compare against bounds that are at most the maximum index value, so no
15/// operation converts a bound it cannot represent.
16///
17/// # Examples
18///
19/// ```
20/// use ps_range::RangeExt;
21///
22/// assert_eq!((..8).clamp_right(6usize), 0..6);
23/// assert_eq!((2..=8usize).end_exclusive(), Some(9));
24/// assert_eq!((2..=u8::MAX).end_exclusive(), None);
25/// assert_eq!((0..=u8::MAX).clamp_to(10, 200), 10..200);
26/// ```
27pub trait RangeExt<Idx = usize>: crate::RangeStart<Idx>
28where
29    Idx: Clone + Ord,
30{
31    /// Returns the inclusive upper bound.
32    ///
33    /// The returned bound is meaningful only for a non-empty range: an empty
34    /// range has no last index, so `(0..0).end_inclusive()` returns `0`.
35    #[must_use]
36    fn end_inclusive(&self) -> Idx;
37
38    /// Returns the exclusive upper bound, or [`None`] if it would exceed the
39    /// maximum index value, that is, if the range ends inclusively at the
40    /// maximum.
41    #[must_use]
42    fn end_exclusive(&self) -> Option<Idx>;
43
44    /// Restricts the range to the bounds `start..end`.
45    ///
46    /// The result is slice-safe: its start does not exceed its end, and a
47    /// window disjoint from the range yields an empty range anchored at the
48    /// computed end.
49    #[inline]
50    #[must_use]
51    fn clamp_to(&self, start: impl Into<Idx>, end: impl Into<Idx>) -> std::ops::Range<Idx> {
52        let window_end = end.into();
53
54        // `None` means the exclusive bound exceeds the maximum index value,
55        // which the window's end cannot, so the window binds.
56        let end = match self.end_exclusive() {
57            Some(end) => end.min(window_end),
58            None => window_end,
59        };
60        let start = self.start().max(start.into()).min(end.clone());
61
62        start..end
63    }
64
65    /// Lowers the upper bound to at most `end`.
66    #[inline]
67    #[must_use]
68    fn clamp_right(&self, end: impl Into<Idx>) -> std::ops::Range<Idx> {
69        self.clamp_to(self.start(), end)
70    }
71}
72
73impl<Idx> RangeExt<Idx> for std::ops::Range<Idx>
74where
75    Idx: Clone + Ord + One + SaturatingSub,
76{
77    #[inline]
78    fn end_inclusive(&self) -> Idx {
79        self.end.saturating_sub(&Idx::one())
80    }
81
82    #[inline]
83    fn end_exclusive(&self) -> Option<Idx> {
84        Some(self.end.clone())
85    }
86}
87
88/// The accessors collapse a drained range, whose endpoint values the
89/// standard library leaves unspecified, to an empty range at its start, so
90/// the clamping operations cannot resurrect it.
91impl<Idx> RangeExt<Idx> for std::ops::RangeInclusive<Idx>
92where
93    Idx: Clone + Ord + One + CheckedAdd,
94{
95    #[inline]
96    fn end_inclusive(&self) -> Idx {
97        self.end().clone()
98    }
99
100    #[inline]
101    fn end_exclusive(&self) -> Option<Idx> {
102        if self.is_empty() {
103            Some(self.start().clone())
104        } else {
105            self.end().checked_add(&Idx::one())
106        }
107    }
108}
109
110impl<Idx> RangeExt<Idx> for std::ops::RangeTo<Idx>
111where
112    Idx: Clone + Ord + num_traits::Zero + One + SaturatingSub,
113{
114    #[inline]
115    fn end_inclusive(&self) -> Idx {
116        self.end.saturating_sub(&Idx::one())
117    }
118
119    #[inline]
120    fn end_exclusive(&self) -> Option<Idx> {
121        Some(self.end.clone())
122    }
123}
124
125impl<Idx> RangeExt<Idx> for std::ops::RangeToInclusive<Idx>
126where
127    Idx: Clone + Ord + num_traits::Zero + One + CheckedAdd,
128{
129    #[inline]
130    fn end_inclusive(&self) -> Idx {
131        self.end.clone()
132    }
133
134    #[inline]
135    fn end_exclusive(&self) -> Option<Idx> {
136        self.end.checked_add(&Idx::one())
137    }
138}
139
140impl<Idx> RangeExt<Idx> for Range<Idx>
141where
142    Idx: Clone + Ord + One + CheckedAdd + SaturatingSub,
143{
144    #[inline]
145    fn end_inclusive(&self) -> Idx {
146        match &self.end {
147            RangeEnd::Inclusive(end) => end.clone(),
148            RangeEnd::Exclusive(end) => end.saturating_sub(&Idx::one()),
149        }
150    }
151
152    #[inline]
153    fn end_exclusive(&self) -> Option<Idx> {
154        match &self.end {
155            RangeEnd::Inclusive(end) => end.checked_add(&Idx::one()),
156            RangeEnd::Exclusive(end) => Some(end.clone()),
157        }
158    }
159}
160
161impl<Idx, T> RangeExt<Idx> for &T
162where
163    Idx: Clone + Ord,
164    T: RangeExt<Idx>,
165{
166    #[inline]
167    fn end_inclusive(&self) -> Idx {
168        (*self).end_inclusive()
169    }
170
171    #[inline]
172    fn end_exclusive(&self) -> Option<Idx> {
173        (*self).end_exclusive()
174    }
175}
176
177impl<Idx, T> RangeExt<Idx> for &mut T
178where
179    Idx: Clone + Ord,
180    T: RangeExt<Idx>,
181{
182    #[inline]
183    fn end_inclusive(&self) -> Idx {
184        (**self).end_inclusive()
185    }
186
187    #[inline]
188    fn end_exclusive(&self) -> Option<Idx> {
189        (**self).end_exclusive()
190    }
191}