rangetools/step.rs
1/// Types are required to implement this trait for ranges of that type to be
2/// iterated through.
3///
4/// Just `std::iter::Step` without the nightly features.
5///
6/// Objects that have a notion of succesor and predecessor operations.
7///
8/// The successor operation moves towards values that compare greater.
9/// The predecessor operation moves towards values that compare lesser.
10pub trait Step: Clone + PartialOrd + Sized {
11 /// Returns the number of succesor steps required to get from `start` to `end`.
12 ///
13 /// Returns `None` if the number of steps would overflow `usize`, or is infinite
14 /// or if `start` > `end`.
15 fn steps_between(start: &Self, end: &Self) -> Option<usize>;
16
17 /// Returns the value that would be obtained by taking the successor of `start`
18 /// `count` times.
19 ///
20 /// Returns `None` if this would overflow the range of values supported by `Self`.
21 fn forward_checked(start: Self, count: usize) -> Option<Self>;
22
23 /// Returns the value that would be obtained by taking the successor of `start`
24 /// `count` times.
25 ///
26 /// If this would overflow the range of values supported by `Self`, this function is
27 /// allowed to panic, wrap, or saturate.
28 /// The suggested behavior is to panic when debug assertions are enabled, and to wrap
29 /// or saturate otherwise.
30 fn forward(start: Self, count: usize) -> Self {
31 Step::forward_checked(start, count).expect("Overflow in `Step::forward`")
32 }
33
34 /// Returns the value that would be obtained by taking the predecessor of `start`
35 /// `count` times.
36 ///
37 /// Returns `None` if this would overflow the range of values supported by `Self`.
38 fn backward_checked(start: Self, count: usize) -> Option<Self>;
39
40 /// Returns the value that would be obtained by taking the predecessor of `start`
41 /// `count` times.
42 ///
43 /// If this would overflow the range of values supported by `Self`, this function is
44 /// allowed to panic, wrap, or saturate.
45 /// The suggested behavior is to panic when debug assertions are enabled, and to wrap
46 /// or saturate otherwise.
47 fn backward(start: Self, count: usize) -> Self {
48 Step::backward_checked(start, count).expect("Overflow in `Step::backward`")
49 }
50}
51
52macro_rules! step_identical_methods {
53 () => {
54 #[inline]
55 #[allow(arithmetic_overflow)]
56 fn forward(start: Self, n: usize) -> Self {
57 // In debug builds, trigger a panic on overflow.
58 // This should optimize completely out in release builds.
59 if Self::forward_checked(start, n).is_none() {
60 let _ = Self::MAX + 1;
61 }
62 // Do wrapping math to allow e.g. `Step::forward(-128i8, 255)`.
63 start.wrapping_add(n as Self)
64 }
65
66 #[inline]
67 #[allow(arithmetic_overflow)]
68 fn backward(start: Self, n: usize) -> Self {
69 // In debug builds, trigger a panic on overflow.
70 // This should optimize completely out in release builds.
71 if Self::backward_checked(start, n).is_none() {
72 let _ = Self::MIN - 1;
73 }
74 // Do wrapping math to allow e.g. `Step::backward(127i8, 255)`.
75 start.wrapping_sub(n as Self)
76 }
77 };
78}
79
80macro_rules! step_integer_impls {
81 {
82 narrower than or same width as usize:
83 $( [ $u_narrower:ident $i_narrower:ident ] ),+;
84 wider than usize:
85 $( [ $u_wider:ident $i_wider:ident ] ),+;
86 } => {
87 $(
88 #[allow(unreachable_patterns)]
89 impl Step for $u_narrower {
90 step_identical_methods!();
91
92 #[inline]
93 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
94 if *start <= *end {
95 // This relies on $u_narrower <= usize
96 Some((*end - *start) as usize)
97 } else {
98 None
99 }
100 }
101
102 #[inline]
103 fn forward_checked(start: Self, n: usize) -> Option<Self> {
104 match Self::try_from(n) {
105 Ok(n) => start.checked_add(n),
106 Err(_) => None, // if n is out of range, `unsigned_start + n` is too
107 }
108 }
109
110 #[inline]
111 fn backward_checked(start: Self, n: usize) -> Option<Self> {
112 match Self::try_from(n) {
113 Ok(n) => start.checked_sub(n),
114 Err(_) => None, // if n is out of range, `unsigned_start - n` is too
115 }
116 }
117 }
118
119 #[allow(unreachable_patterns)]
120 impl Step for $i_narrower {
121 step_identical_methods!();
122
123 #[inline]
124 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
125 if *start <= *end {
126 // This relies on $i_narrower <= usize
127 //
128 // Casting to isize extends the width but preserves the sign.
129 // Use wrapping_sub in isize space and cast to usize to compute
130 // the difference that might not fit inside the range of isize.
131 Some((*end as isize).wrapping_sub(*start as isize) as usize)
132 } else {
133 None
134 }
135 }
136
137 #[inline]
138 fn forward_checked(start: Self, n: usize) -> Option<Self> {
139 match $u_narrower::try_from(n) {
140 Ok(n) => {
141 // Wrapping handles cases like
142 // `Step::forward(-120_i8, 200) == Some(80_i8)`,
143 // even though 200 is out of range for i8.
144 let wrapped = start.wrapping_add(n as Self);
145 if wrapped >= start {
146 Some(wrapped)
147 } else {
148 None // Addition overflowed
149 }
150 }
151 // If n is out of range of e.g. u8,
152 // then it is bigger than the entire range for i8 is wide
153 // so `any_i8 + n` necessarily overflows i8.
154 Err(_) => None,
155 }
156 }
157
158 #[inline]
159 fn backward_checked(start: Self, n: usize) -> Option<Self> {
160 match $u_narrower::try_from(n) {
161 Ok(n) => {
162 // Wrapping handles cases like
163 // `Step::forward(-120_i8, 200) == Some(80_i8)`,
164 // even though 200 is out of range for i8.
165 let wrapped = start.wrapping_sub(n as Self);
166 if wrapped <= start {
167 Some(wrapped)
168 } else {
169 None // Subtraction overflowed
170 }
171 }
172 // If n is out of range of e.g. u8,
173 // then it is bigger than the entire range for i8 is wide
174 // so `any_i8 - n` necessarily overflows i8.
175 Err(_) => None,
176 }
177 }
178 }
179 )+
180
181 $(
182 #[allow(unreachable_patterns)]
183 impl Step for $u_wider {
184 step_identical_methods!();
185
186 #[inline]
187 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
188 if *start <= *end {
189 usize::try_from(*end - *start).ok()
190 } else {
191 None
192 }
193 }
194
195 #[inline]
196 fn forward_checked(start: Self, n: usize) -> Option<Self> {
197 start.checked_add(n as Self)
198 }
199
200 #[inline]
201 fn backward_checked(start: Self, n: usize) -> Option<Self> {
202 start.checked_sub(n as Self)
203 }
204 }
205
206 #[allow(unreachable_patterns)]
207 impl Step for $i_wider {
208 step_identical_methods!();
209
210 #[inline]
211 fn steps_between(start: &Self, end: &Self) -> Option<usize> {
212 if *start <= *end {
213 match end.checked_sub(*start) {
214 Some(result) => usize::try_from(result).ok(),
215 // If the difference is too big for e.g. i128,
216 // it's also gonna be too big for usize with fewer bits.
217 None => None,
218 }
219 } else {
220 None
221 }
222 }
223
224 #[inline]
225 fn forward_checked(start: Self, n: usize) -> Option<Self> {
226 start.checked_add(n as Self)
227 }
228
229 #[inline]
230 fn backward_checked(start: Self, n: usize) -> Option<Self> {
231 start.checked_sub(n as Self)
232 }
233 }
234 )+
235 };
236}
237
238#[cfg(target_pointer_width = "64")]
239step_integer_impls! {
240 narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [u64 i64], [usize isize];
241 wider than usize: [u128 i128];
242}
243
244#[cfg(target_pointer_width = "32")]
245step_integer_impls! {
246 narrower than or same width as usize: [u8 i8], [u16 i16], [u32 i32], [usize isize];
247 wider than usize: [u64 i64], [u128 i128];
248}
249
250#[cfg(target_pointer_width = "16")]
251step_integer_impls! {
252 narrower than or same width as usize: [u8 i8], [u16 i16], [usize isize];
253 wider than usize: [u32 i32], [u64 i64], [u128 i128];
254}
255
256impl Step for char {
257 #[inline]
258 fn steps_between(&start: &char, &end: &char) -> Option<usize> {
259 let start = start as u32;
260 let end = end as u32;
261 if start <= end {
262 let count = end - start;
263 if start < 0xD800 && 0xE000 <= end {
264 usize::try_from(count - 0x800).ok()
265 } else {
266 usize::try_from(count).ok()
267 }
268 } else {
269 None
270 }
271 }
272
273 #[inline]
274 fn forward_checked(start: char, count: usize) -> Option<char> {
275 let start = start as u32;
276 let mut res = Step::forward_checked(start, count)?;
277 if start < 0xD800 && 0xD800 <= res {
278 res = Step::forward_checked(res, 0x800)?;
279 }
280 if res <= char::MAX as u32 {
281 // SAFETY: res is a valid unicode scalar
282 // (below 0x110000 and not in 0xD800..0xE000)
283 Some(unsafe { char::from_u32_unchecked(res) })
284 } else {
285 None
286 }
287 }
288
289 #[inline]
290 fn backward_checked(start: char, count: usize) -> Option<char> {
291 let start = start as u32;
292 let mut res = Step::backward_checked(start, count)?;
293 if start >= 0xE000 && 0xE000 > res {
294 res = Step::backward_checked(res, 0x800)?;
295 }
296 // SAFETY: res is a valid unicode scalar
297 // (below 0x110000 and not in 0xD800..0xE000)
298 Some(unsafe { char::from_u32_unchecked(res) })
299 }
300}