1use std::fmt;
14use std::marker::PhantomData;
15use std::num::NonZeroU32;
16
17pub struct Idx<T> {
25 raw: NonZeroU32,
26 _marker: PhantomData<fn() -> T>,
27}
28
29impl<T> Idx<T> {
30 pub const MAX: u32 = u32::MAX - 1;
32
33 #[inline]
41 pub const fn new(raw: u32) -> Self {
42 assert!(raw <= Self::MAX, "index out of range");
43 match NonZeroU32::new(raw + 1) {
44 Some(raw) => Self { raw, _marker: PhantomData },
45 None => unreachable!(),
46 }
47 }
48
49 #[inline]
55 pub fn from_usize(raw: usize) -> Self {
56 Self::new(u32::try_from(raw).expect("index out of range"))
57 }
58
59 #[inline]
61 pub const fn raw(self) -> u32 {
62 self.raw.get() - 1
63 }
64
65 #[inline]
67 pub const fn index(self) -> usize {
68 self.raw() as usize
69 }
70}
71
72impl<T> Clone for Idx<T> {
76 #[inline]
77 fn clone(&self) -> Self {
78 *self
79 }
80}
81
82impl<T> Copy for Idx<T> {}
83
84impl<T> PartialEq for Idx<T> {
85 #[inline]
86 fn eq(&self, other: &Self) -> bool {
87 self.raw == other.raw
88 }
89}
90
91impl<T> Eq for Idx<T> {}
92
93impl<T> PartialOrd for Idx<T> {
94 #[inline]
95 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
96 Some(self.cmp(other))
97 }
98}
99
100impl<T> Ord for Idx<T> {
101 #[inline]
102 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
103 self.raw.cmp(&other.raw)
104 }
105}
106
107impl<T> std::hash::Hash for Idx<T> {
108 #[inline]
109 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
110 self.raw.hash(state);
111 }
112}
113
114impl<T> fmt::Debug for Idx<T> {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 let name = std::any::type_name::<T>();
119 let short = name.rsplit("::").next().unwrap_or(name);
120 write!(f, "{short}#{}", self.raw())
121 }
122}
123
124pub struct IdxRange<T> {
129 start: u32,
130 end: u32,
131 _marker: PhantomData<fn() -> T>,
132}
133
134impl<T> Clone for IdxRange<T> {
137 #[inline]
138 fn clone(&self) -> Self {
139 *self
140 }
141}
142
143impl<T> Copy for IdxRange<T> {}
144
145impl<T> PartialEq for IdxRange<T> {
146 #[inline]
147 fn eq(&self, other: &Self) -> bool {
148 self.start == other.start && self.end == other.end
149 }
150}
151
152impl<T> Eq for IdxRange<T> {}
153
154impl<T> fmt::Debug for IdxRange<T> {
155 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156 let name = std::any::type_name::<T>();
157 let short = name.rsplit("::").next().unwrap_or(name);
158 write!(f, "{short}#{}..{}", self.start, self.end)
159 }
160}
161
162impl<T> IdxRange<T> {
163 pub const EMPTY: Self = Self { start: 0, end: 0, _marker: PhantomData };
170
171 #[inline]
177 pub fn new(start: Idx<T>, end: Idx<T>) -> Self {
178 assert!(start.raw() <= end.raw(), "reversed index range");
179 Self { start: start.raw(), end: end.raw(), _marker: PhantomData }
180 }
181
182 #[inline]
184 pub fn empty_at(at: Idx<T>) -> Self {
185 Self { start: at.raw(), end: at.raw(), _marker: PhantomData }
186 }
187
188 #[inline]
190 pub const fn len(self) -> usize {
191 (self.end - self.start) as usize
192 }
193
194 #[inline]
196 pub const fn is_empty(self) -> bool {
197 self.start == self.end
198 }
199
200 pub fn iter(self) -> impl Iterator<Item = Idx<T>> {
202 (self.start..self.end).map(Idx::new)
203 }
204
205 #[inline]
207 pub const fn as_usize_range(self) -> std::ops::Range<usize> {
208 self.start as usize..self.end as usize
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 struct Block;
217 struct Inst;
218
219 #[test]
220 fn an_index_is_four_bytes_and_so_is_an_optional_one() {
221 assert_eq!(size_of::<Idx<Block>>(), 4);
222 assert_eq!(size_of::<Option<Idx<Block>>>(), 4);
223 }
224
225 #[test]
226 fn round_trips_through_usize() {
227 let i = Idx::<Inst>::from_usize(7);
228 assert_eq!(i.index(), 7);
229 assert_eq!(i.raw(), 7);
230 }
231
232 #[test]
233 fn debug_names_the_table() {
234 assert_eq!(format!("{:?}", Idx::<Block>::new(3)), "Block#3");
235 }
236
237 #[test]
238 fn a_range_iterates_half_open() {
239 let r = IdxRange::new(Idx::<Inst>::new(2), Idx::<Inst>::new(5));
240 let got: Vec<u32> = r.iter().map(Idx::raw).collect();
241 assert_eq!(got, vec![2, 3, 4]);
242 assert_eq!(r.len(), 3);
243 assert_eq!(r.as_usize_range(), 2..5);
244 }
245
246 #[test]
247 fn an_empty_range_is_empty() {
248 let r = IdxRange::empty_at(Idx::<Inst>::new(9));
249 assert!(r.is_empty());
250 assert_eq!(r.iter().count(), 0);
251 }
252
253 #[test]
254 fn the_empty_range_slices_an_empty_table() {
255 let r = IdxRange::<Inst>::EMPTY;
256 assert!(r.is_empty());
257 assert_eq!(r.len(), 0);
258 let table: Vec<u8> = Vec::new();
259 assert!(table[r.as_usize_range()].is_empty());
260 }
261
262 #[test]
263 #[should_panic(expected = "reversed index range")]
264 fn a_reversed_range_is_rejected() {
265 let _ = IdxRange::new(Idx::<Inst>::new(5), Idx::<Inst>::new(2));
266 }
267
268 #[test]
269 #[should_panic(expected = "index out of range")]
270 fn the_niche_value_is_rejected() {
271 let _ = Idx::<Inst>::new(u32::MAX);
272 }
273}