exhaustive_map/
range.rs

1use core::{
2    marker::PhantomData,
3    ops::{Add, Sub},
4};
5
6use generic_array::ArrayLength;
7
8use crate::{
9    Finite, FitsInUsize,
10    typenum::{B1, Unsigned},
11};
12
13/// A `usize` value that is guaranteed to be in the range `A..B`.
14///
15/// Common methods are in the [`InRangeBounds`] trait implementation.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17pub struct InRange<A: Unsigned, B: Unsigned>
18where
19    B: Sub<A>,
20    <B as Sub<A>>::Output: Unsigned,
21{
22    value: usize,
23    _phantom: PhantomData<(A, B)>,
24}
25
26/// A `usize` value that is guaranteed to be in the range `A..=B`.
27///
28/// Common methods are in the [`InRangeBounds`] trait implementation.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct InRangeInclusive<A: Unsigned, B: Unsigned>
31where
32    B: Sub<A>,
33    <B as Sub<A>>::Output: Add<B1>,
34    <<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength,
35{
36    value: usize,
37    _phantom: PhantomData<(A, B)>,
38}
39
40pub trait InRangeBounds: Copy + Sized {
41    /// The smallest value representable (if `INHABITANTS` is non-zero).
42    type MIN: Unsigned;
43
44    /// The number of values representable.
45    type INHABITANTS: ArrayLength + FitsInUsize;
46
47    /// Creates a value without checking whether the value is in range. This
48    /// results in undefined behavior if the value is not in range.
49    ///
50    /// # Safety
51    /// `i` must satisfy `Self::MIN <= i` and `i < Self::MIN +
52    /// Self::INHABITANTS`.
53    #[must_use]
54    unsafe fn new_unchecked(i: usize) -> Self;
55
56    /// Returns the value as a `usize`.
57    #[must_use]
58    fn get(self) -> usize;
59
60    /// Same as `InRangeBounds::new(Self::MIN + i)`.
61    #[must_use]
62    fn new_from_start_offset(offset: usize) -> Option<Self> {
63        Self::new(Self::MIN::USIZE + offset)
64    }
65
66    /// Returns the offset from `Self::MIN` if `i` is in range.
67    #[must_use]
68    fn offset_from_start(i: usize) -> Option<usize> {
69        let offset = i.checked_sub(Self::MIN::USIZE)?;
70        if offset < Self::INHABITANTS::USIZE {
71            Some(offset)
72        } else {
73            None
74        }
75    }
76
77    /// Returns whether `i` is in range.
78    #[must_use]
79    fn in_bounds(i: usize) -> bool {
80        Self::offset_from_start(i).is_some()
81    }
82
83    /// Creates a value if the given value is in range.
84    #[must_use]
85    fn new(i: usize) -> Option<Self> {
86        if Self::in_bounds(i) {
87            // SAFETY: `i` is in bounds.
88            Some(unsafe { Self::new_unchecked(i) })
89        } else {
90            None
91        }
92    }
93}
94
95impl<A: Unsigned, B: Unsigned> InRangeBounds for InRange<A, B>
96where
97    B: Sub<A>,
98    <B as Sub<A>>::Output: ArrayLength + FitsInUsize,
99{
100    type MIN = A;
101    type INHABITANTS = <B as Sub<A>>::Output;
102
103    unsafe fn new_unchecked(i: usize) -> Self {
104        Self {
105            value: i,
106            _phantom: PhantomData,
107        }
108    }
109
110    fn get(self) -> usize {
111        self.value
112    }
113}
114
115impl<A: Unsigned, B: Unsigned> InRangeBounds for InRangeInclusive<A, B>
116where
117    B: Sub<A>,
118    <B as Sub<A>>::Output: Add<B1>,
119    <<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength + FitsInUsize,
120{
121    type MIN = A;
122    type INHABITANTS = <<B as Sub<A>>::Output as Add<B1>>::Output;
123
124    unsafe fn new_unchecked(i: usize) -> Self {
125        Self {
126            value: i,
127            _phantom: PhantomData,
128        }
129    }
130
131    fn get(self) -> usize {
132        self.value
133    }
134}
135
136impl<A: Unsigned, B: Unsigned> Finite for InRange<A, B>
137where
138    B: Sub<A>,
139    <B as Sub<A>>::Output: ArrayLength + FitsInUsize,
140{
141    type INHABITANTS = <Self as InRangeBounds>::INHABITANTS;
142
143    fn to_usize(&self) -> usize {
144        self.get() - <Self as InRangeBounds>::MIN::USIZE
145    }
146
147    fn from_usize(i: usize) -> Option<Self> {
148        Self::new_from_start_offset(i)
149    }
150}
151
152impl<A: Unsigned, B: Unsigned> Finite for InRangeInclusive<A, B>
153where
154    B: Sub<A>,
155    <B as Sub<A>>::Output: Add<B1>,
156    <<B as Sub<A>>::Output as Add<B1>>::Output: ArrayLength + FitsInUsize,
157{
158    type INHABITANTS = <Self as InRangeBounds>::INHABITANTS;
159
160    fn to_usize(&self) -> usize {
161        self.get() - <Self as InRangeBounds>::MIN::USIZE
162    }
163
164    fn from_usize(i: usize) -> Option<Self> {
165        Self::new_from_start_offset(i)
166    }
167}
168
169#[cfg(all(test, feature = "std"))]
170mod test {
171    use std::{fmt::Debug, ops::RangeBounds};
172
173    use super::*;
174    use crate::typenum::{Pow, Sub1, U, U0, U1, U3, U256};
175
176    type UsizeMax = Sub1<<U256 as Pow<U<{ std::mem::size_of::<usize>() }>>>::Output>;
177
178    fn test_range<T: InRangeBounds + Debug + PartialEq, R: RangeBounds<usize>>(expected_range: R) {
179        for i in (0..10).chain(usize::MAX - 10..=usize::MAX) {
180            let v = T::new(i);
181            if expected_range.contains(&i) {
182                assert_eq!(v.map(InRangeBounds::get), Some(i));
183            } else {
184                assert_eq!(v, None);
185            }
186        }
187    }
188
189    #[test]
190    fn test_in_range_full() {
191        test_range::<InRange<U0, UsizeMax>, _>(0..usize::MAX);
192    }
193
194    #[test]
195    fn test_in_range_inclusive_almost_full() {
196        test_range::<InRangeInclusive<U1, UsizeMax>, _>(1..=usize::MAX);
197    }
198
199    #[test]
200    fn test_in_range() {
201        test_range::<InRange<U1, U3>, _>(1..3);
202    }
203
204    #[test]
205    fn test_in_range_inclusive() {
206        test_range::<InRangeInclusive<U1, U3>, _>(1..=3);
207    }
208}