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}