exhaustive_map/
range.rs

1use crate::Finite;
2
3/// A `usize` value that is guaranteed to be in the range `A..B`.
4///
5/// Common methods are in the [`InRangeBounds`] trait implementation.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct InRange<const A: usize, const B: usize>(usize);
8
9/// A `usize` value that is guaranteed to be in the range `A..=B`.
10///
11/// Common methods are in the [`InRangeBounds`] trait implementation.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct InRangeInclusive<const A: usize, const B: usize>(usize);
14
15pub trait InRangeBounds: Copy + Sized {
16    /// The smallest value representable (if `INHABITANTS` is non-zero).
17    const MIN: usize;
18
19    /// The number of values representable.
20    const INHABITANTS: usize;
21
22    /// Creates a value without checking whether the value is in range. This results in undefined behavior if the value is not in range.
23    ///
24    /// # Safety
25    /// `i` must satisfy `Self::MIN <= i` and `i < Self::MIN + Self::INHABITANTS`.
26    #[must_use]
27    unsafe fn new_unchecked(i: usize) -> Self;
28
29    /// Returns the value as a `usize`.
30    #[must_use]
31    fn get(self) -> usize;
32
33    /// Same as `InRangeBounds::new(Self::MIN + i)`.
34    #[must_use]
35    fn new_from_start_offset(offset: usize) -> Option<Self> {
36        Self::new(Self::MIN + offset)
37    }
38
39    /// Returns the offset from `Self::MIN` if `i` is in range.
40    #[must_use]
41    fn offset_from_start(i: usize) -> Option<usize> {
42        let offset = i.checked_sub(Self::MIN)?;
43        if offset < Self::INHABITANTS {
44            Some(offset)
45        } else {
46            None
47        }
48    }
49
50    /// Returns whether `i` is in range.
51    #[must_use]
52    fn in_bounds(i: usize) -> bool {
53        Self::offset_from_start(i).is_some()
54    }
55
56    /// Creates a value if the given value is in range.
57    #[must_use]
58    fn new(i: usize) -> Option<Self> {
59        if Self::in_bounds(i) {
60            // SAFETY: `i` is in bounds.
61            Some(unsafe { Self::new_unchecked(i) })
62        } else {
63            None
64        }
65    }
66}
67
68impl<const A: usize, const B: usize> InRangeBounds for InRange<A, B> {
69    const MIN: usize = A;
70    const INHABITANTS: usize = B - A;
71
72    unsafe fn new_unchecked(i: usize) -> Self {
73        Self(i)
74    }
75
76    fn get(self) -> usize {
77        self.0
78    }
79}
80
81impl<const A: usize, const B: usize> InRangeBounds for InRangeInclusive<A, B> {
82    const MIN: usize = A;
83    const INHABITANTS: usize = B - A + 1;
84
85    unsafe fn new_unchecked(i: usize) -> Self {
86        Self(i)
87    }
88
89    fn get(self) -> usize {
90        self.0
91    }
92}
93
94impl<const A: usize, const B: usize> Finite for InRange<A, B> {
95    const INHABITANTS: usize = <Self as InRangeBounds>::INHABITANTS;
96
97    fn to_usize(&self) -> usize {
98        self.get() - Self::MIN
99    }
100
101    fn from_usize(i: usize) -> Option<Self> {
102        Self::new_from_start_offset(i)
103    }
104}
105
106impl<const A: usize, const B: usize> Finite for InRangeInclusive<A, B> {
107    const INHABITANTS: usize = <Self as InRangeBounds>::INHABITANTS;
108
109    fn to_usize(&self) -> usize {
110        self.get() - Self::MIN
111    }
112
113    fn from_usize(i: usize) -> Option<Self> {
114        Self::new_from_start_offset(i)
115    }
116}
117
118#[cfg(test)]
119mod test {
120    use std::{fmt::Debug, ops::RangeBounds};
121
122    use super::*;
123
124    fn test_range<T: InRangeBounds + Debug + PartialEq, R: RangeBounds<usize>>(expected_range: R) {
125        for i in (0..10).chain(usize::MAX - 10..=usize::MAX) {
126            let v = T::new(i);
127            if expected_range.contains(&i) {
128                assert_eq!(v.map(InRangeBounds::get), Some(i));
129            } else {
130                assert_eq!(v, None);
131            }
132        }
133    }
134
135    #[test]
136    fn test_in_range_full() {
137        test_range::<InRange<0, { usize::MAX }>, _>(0..usize::MAX);
138    }
139
140    #[test]
141    fn test_in_range_inclusive_almost_full() {
142        test_range::<InRangeInclusive<1, { usize::MAX }>, _>(1..=usize::MAX);
143    }
144
145    #[test]
146    fn test_in_range() {
147        test_range::<InRange<1, 3>, _>(1..3);
148    }
149
150    #[test]
151    fn test_in_range_inclusive() {
152        test_range::<InRangeInclusive<1, 3>, _>(1..=3);
153    }
154}