1use crate::Finite;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct InRange<const A: usize, const B: usize>(usize);
8
9#[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 const MIN: usize;
18
19 const INHABITANTS: usize;
21
22 #[must_use]
27 unsafe fn new_unchecked(i: usize) -> Self;
28
29 #[must_use]
31 fn get(self) -> usize;
32
33 #[must_use]
35 fn new_from_start_offset(offset: usize) -> Option<Self> {
36 Self::new(Self::MIN + offset)
37 }
38
39 #[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 #[must_use]
52 fn in_bounds(i: usize) -> bool {
53 Self::offset_from_start(i).is_some()
54 }
55
56 #[must_use]
58 fn new(i: usize) -> Option<Self> {
59 if Self::in_bounds(i) {
60 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}