range_cmp/
lib.rs

1// Copyright 2023 Developers of the range_cmp project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! This crate provides the [`RangeComparable`] trait on all types that implement [`Ord`].
10//! This traits exposes a [`range_cmp`](RangeComparable::range_cmp) associated method that allows
11//! comparing a value with a range of values:
12//!
13//! ```
14//! use range_cmp::{RangeComparable, RangeOrdering};
15//! assert_eq!(15.range_cmp(20..30), RangeOrdering::Below);
16//! assert_eq!(25.range_cmp(20..30), RangeOrdering::Inside);
17//! assert_eq!(35.range_cmp(20..30), RangeOrdering::Above);
18//! ```
19
20use std::borrow::Borrow;
21use std::ops::{Bound, RangeBounds};
22
23/// Return type for [`RangeComparable::range_cmp`].
24#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
25pub enum RangeOrdering {
26    /// The value is below (all) the range. For instance, `-1` is below the range `0..42`.
27    Below,
28    /// The value is contained inside the range. For instance, `34` is inside the range `0..42`.
29    Inside,
30    /// The value is above (all) the range. For instance, `314` is above the range `0..42`.
31    Above,
32}
33
34// suggestion from @benschulz https://internals.rust-lang.org/t/implement-rangebounds-for-range/19704/3
35/// Helper trait to allow passing a range as either a owned value or a reference.
36///
37/// For instance:
38///
39/// ```
40/// use std::ops::RangeBounds;
41/// use range_cmp::BorrowRange;
42/// fn f<T, R: RangeBounds<T>, B: BorrowRange<T, R>>(range: B) {
43///     let range = range.borrow();
44///     // ...
45/// }
46/// ```
47///
48/// With concrete type such as [`i32`], this would be achieved by taking a generic type `T` with
49/// the bound `T: Borrow<i32>`. So we might be tempted to do the same with the [`RangeBounds`]
50/// trait:
51///
52/// ```
53/// use std::borrow::Borrow;
54/// use std::ops::RangeBounds;
55/// fn f<R: RangeBounds<i32>, B: Borrow<R>>(range: B) {
56///     let range = range.borrow();
57///     // ...
58/// }
59/// f(0..42)
60/// ```
61///
62/// However, this fails to compile when passing a reference:
63///
64/// ```compile_fail,E0282
65/// # use std::borrow::Borrow;
66/// # use std::ops::RangeBounds;
67/// # fn f<R: RangeBounds<i32>, B: Borrow<R>>(range: B) {
68/// #     let range = range.borrow();
69/// #     // ...
70/// # }
71/// f(&(0..42))
72/// ```
73///
74/// The compilation output is:
75///
76/// ```shell
77///   | f(&(0..42))
78///   | ^ cannot infer type of the type parameter `R` declared on the function `f`
79/// ```
80///
81/// Indeed, although we understand we want to pass a [`Range`](std::ops::Range)`<`[`i32`]`>` by
82/// reference, the compiler need to assume that other types could yield a
83/// `&`[`Range`](std::ops::Range)`<`[`i32`]`>` when borrowed.
84pub trait BorrowRange<T: ?Sized, R>: Borrow<R> {}
85impl<T, R: RangeBounds<T>> BorrowRange<T, R> for R {}
86impl<T, R: RangeBounds<T>> BorrowRange<T, R> for &R {}
87
88/// Trait to provide the [`range_cmp`](RangeComparable::range_cmp) method, which allows comparing
89/// the type to a range. A blanket implementation is provided for all types that implement the
90/// [`Ord`] trait.
91pub trait RangeComparable {
92    /// Compare the value to a range of values. Returns whether the value is below, inside or above
93    /// the range.
94    ///
95    /// ```
96    /// use range_cmp::{RangeComparable, RangeOrdering};
97    /// assert_eq!(15.range_cmp(20..30), RangeOrdering::Below);
98    /// assert_eq!(25.range_cmp(20..30), RangeOrdering::Inside);
99    /// assert_eq!(35.range_cmp(20..30), RangeOrdering::Above);
100    /// ```
101    fn range_cmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering;
102}
103
104impl<T: Ord> RangeComparable for T {
105    fn range_cmp<R: RangeBounds<Self>, B: BorrowRange<Self, R>>(&self, range: B) -> RangeOrdering {
106        let range = range.borrow();
107
108        if range.contains(self) {
109            return RangeOrdering::Inside;
110        }
111
112        if match range.start_bound() {
113            Bound::Included(key) => self < key,
114            Bound::Excluded(key) => self <= key,
115            _ => false,
116        } {
117            return RangeOrdering::Below;
118        }
119
120        if match range.end_bound() {
121            Bound::Included(key) => self > key,
122            Bound::Excluded(key) => self >= key,
123            _ => false,
124        } {
125            return RangeOrdering::Above;
126        }
127
128        unreachable!()
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn range_full() {
138        // 1 is inside ]-inf, inf[
139        assert_eq!(1.range_cmp(..), RangeOrdering::Inside);
140    }
141
142    #[test]
143    fn range_from() {
144        // 1 is inside [1, +inf[
145        assert_eq!(1.range_cmp(1..), RangeOrdering::Inside);
146        assert_eq!(1.range_cmp(&1..), RangeOrdering::Inside);
147
148        // 1 is below [2, +inf[
149        assert_eq!(1.range_cmp(2..), RangeOrdering::Below);
150        assert_eq!(1.range_cmp(&2..), RangeOrdering::Below);
151    }
152
153    #[test]
154    fn range_to() {
155        // 1 is above ]-inf, 1[
156        assert_eq!(1.range_cmp(..1), RangeOrdering::Above);
157        assert_eq!(1.range_cmp(..&1), RangeOrdering::Above);
158
159        // 1 is inside ]-inf, 2[
160        assert_eq!(1.range_cmp(..2), RangeOrdering::Inside);
161        assert_eq!(1.range_cmp(..&2), RangeOrdering::Inside);
162    }
163
164    #[test]
165    fn range() {
166        // 1 is above [0, 1[
167        assert_eq!(1.range_cmp(0..1), RangeOrdering::Above);
168        assert_eq!(1.range_cmp(&0..&1), RangeOrdering::Above);
169
170        // 1 is inside [1, 2[
171        assert_eq!(1.range_cmp(1..2), RangeOrdering::Inside);
172        assert_eq!(1.range_cmp(&1..&2), RangeOrdering::Inside);
173
174        // 1 is below [2, 3[
175        assert_eq!(1.range_cmp(2..3), RangeOrdering::Below);
176        assert_eq!(1.range_cmp(&2..&3), RangeOrdering::Below);
177    }
178
179    #[test]
180    fn range_inclusive() {
181        // 1 is above [0, 0]
182        assert_eq!(1.range_cmp(0..=0), RangeOrdering::Above);
183        assert_eq!(1.range_cmp(&0..=&0), RangeOrdering::Above);
184
185        // 1 is inside [1, 1]
186        assert_eq!(1.range_cmp(1..=1), RangeOrdering::Inside);
187        assert_eq!(1.range_cmp(&1..=&1), RangeOrdering::Inside);
188
189        // 1 is below [2, 2]
190        assert_eq!(1.range_cmp(2..=2), RangeOrdering::Below);
191        assert_eq!(1.range_cmp(&2..=&2), RangeOrdering::Below);
192    }
193
194    #[test]
195    fn range_to_inclusive() {
196        // 1 is above ]-inf, 0]
197        assert_eq!(1.range_cmp(..=0), RangeOrdering::Above);
198        assert_eq!(1.range_cmp(..=&0), RangeOrdering::Above);
199
200        // 1 is inside ]-inf, 1
201        assert_eq!(1.range_cmp(..=1), RangeOrdering::Inside);
202        assert_eq!(1.range_cmp(..=&1), RangeOrdering::Inside);
203    }
204
205    #[test]
206    fn bounds_full() {
207        // 1 is inside ]-inf, inf[
208        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Unbounded, Bound::Unbounded);
209        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
210    }
211
212    #[test]
213    fn bounds_from() {
214        // 1 is inside [1, +inf[
215        let bounds = (Bound::Included(1), Bound::Unbounded);
216        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
217
218        let bounds = (Bound::Included(&1), Bound::Unbounded);
219        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
220
221        // 1 is below [2, +inf[
222        let bounds = (Bound::Included(2), Bound::Unbounded);
223        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
224
225        let bounds = (Bound::Included(&2), Bound::Unbounded);
226        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
227    }
228
229    #[test]
230    fn bounds_to() {
231        // 1 is above ]-inf, 1[
232        let bounds = (Bound::Unbounded, Bound::Excluded(1));
233        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
234
235        let bounds = (Bound::Unbounded, Bound::Excluded(&1));
236        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
237
238        // 1 is inside ]-inf, 2[
239        let bounds = (Bound::Unbounded, Bound::Excluded(2));
240        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
241
242        let bounds = (Bound::Unbounded, Bound::Excluded(&2));
243        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
244    }
245
246    #[test]
247    fn bounds() {
248        // 1 is above [0, 1[
249        let bounds = (Bound::Included(0), Bound::Excluded(1));
250        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
251
252        let bounds = (Bound::Included(&0), Bound::Excluded(&1));
253        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
254
255        // 1 is inside [1, 2[
256        let bounds = (Bound::Included(1), Bound::Excluded(2));
257        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
258
259        let bounds = (Bound::Included(&1), Bound::Excluded(&2));
260        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
261
262        // 1 is below [2, 3[
263        let bounds = (Bound::Included(2), Bound::Excluded(3));
264        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
265
266        let bounds = (Bound::Included(&2), Bound::Excluded(&3));
267        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
268    }
269
270    #[test]
271    fn bounds_inclusive() {
272        // 1 is above [0, 0]
273        let bounds = (Bound::Included(0), Bound::Included(0));
274        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
275
276        let bounds = (Bound::Included(&0), Bound::Included(&0));
277        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
278
279        // 1 is inside [1, 1]
280        let bounds = (Bound::Included(1), Bound::Included(1));
281        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
282
283        let bounds = (Bound::Included(&1), Bound::Included(&1));
284        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
285
286        // 1 is below [2, 2]
287        let bounds = (Bound::Included(2), Bound::Included(2));
288        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
289
290        let bounds = (Bound::Included(&2), Bound::Included(&2));
291        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
292    }
293
294    #[test]
295    fn bounds_to_inclusive() {
296        // 1 is above ]-inf, 0]
297        let bounds = (Bound::Unbounded, Bound::Included(0));
298        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
299
300        let bounds = (Bound::Unbounded, Bound::Included(&0));
301        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
302
303        // 1 is inside ]-inf, 1]
304        let bounds = (Bound::Unbounded, Bound::Included(1));
305        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
306
307        let bounds = (Bound::Unbounded, Bound::Included(&1));
308        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
309    }
310
311    #[test]
312    fn bounds_exclusive_inclusive() {
313        // 1 is above ]-1, 0]
314        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(-1), Bound::Included(0));
315        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
316
317        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&-1), Bound::Included(&0));
318        assert_eq!(1.range_cmp(bounds), RangeOrdering::Above);
319
320        // 1 is inside ]0, 1]
321        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(0), Bound::Included(1));
322        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
323
324        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&0), Bound::Included(&1));
325        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
326
327        // 1 is below ]1, 2]
328        let bounds: (Bound<i32>, Bound<i32>) = (Bound::Excluded(1), Bound::Included(2));
329        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
330
331        let bounds: (Bound<&i32>, Bound<&i32>) = (Bound::Excluded(&1), Bound::Included(&2));
332        assert_eq!(1.range_cmp(bounds), RangeOrdering::Below);
333    }
334
335    #[test]
336    fn bounds_as_reference() {
337        let bounds = 0..2;
338        assert_eq!(1.range_cmp(&bounds), RangeOrdering::Inside);
339        assert_eq!(1.range_cmp(bounds), RangeOrdering::Inside);
340    }
341
342    #[test]
343    fn empty_ranges() {
344        // 0 is above [0, 0[
345        assert_eq!(0.range_cmp(0..0), RangeOrdering::Above);
346        assert_eq!(0.range_cmp(&0..&0), RangeOrdering::Above);
347
348        // 0u32 is above [-inf, 0u32[
349        assert_eq!(0.range_cmp(..0u32), RangeOrdering::Above);
350        assert_eq!(0.range_cmp(..&0u32), RangeOrdering::Above);
351
352        // 30 is below [45, 35[
353        assert_eq!(30.range_cmp(45..35), RangeOrdering::Below);
354        assert_eq!(30.range_cmp(&45..&35), RangeOrdering::Below);
355
356        // 30 is above [25, 15[
357        assert_eq!(30.range_cmp(25..15), RangeOrdering::Above);
358        assert_eq!(30.range_cmp(&25..&15), RangeOrdering::Above);
359    }
360}