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 #[inline]
169 pub fn new(start: Idx<T>, end: Idx<T>) -> Self {
170 assert!(start.raw() <= end.raw(), "reversed index range");
171 Self { start: start.raw(), end: end.raw(), _marker: PhantomData }
172 }
173
174 #[inline]
176 pub fn empty_at(at: Idx<T>) -> Self {
177 Self { start: at.raw(), end: at.raw(), _marker: PhantomData }
178 }
179
180 #[inline]
182 pub const fn len(self) -> usize {
183 (self.end - self.start) as usize
184 }
185
186 #[inline]
188 pub const fn is_empty(self) -> bool {
189 self.start == self.end
190 }
191
192 pub fn iter(self) -> impl Iterator<Item = Idx<T>> {
194 (self.start..self.end).map(Idx::new)
195 }
196
197 #[inline]
199 pub const fn as_usize_range(self) -> std::ops::Range<usize> {
200 self.start as usize..self.end as usize
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 struct Block;
209 struct Inst;
210
211 #[test]
212 fn an_index_is_four_bytes_and_so_is_an_optional_one() {
213 assert_eq!(size_of::<Idx<Block>>(), 4);
214 assert_eq!(size_of::<Option<Idx<Block>>>(), 4);
215 }
216
217 #[test]
218 fn round_trips_through_usize() {
219 let i = Idx::<Inst>::from_usize(7);
220 assert_eq!(i.index(), 7);
221 assert_eq!(i.raw(), 7);
222 }
223
224 #[test]
225 fn debug_names_the_table() {
226 assert_eq!(format!("{:?}", Idx::<Block>::new(3)), "Block#3");
227 }
228
229 #[test]
230 fn a_range_iterates_half_open() {
231 let r = IdxRange::new(Idx::<Inst>::new(2), Idx::<Inst>::new(5));
232 let got: Vec<u32> = r.iter().map(Idx::raw).collect();
233 assert_eq!(got, vec![2, 3, 4]);
234 assert_eq!(r.len(), 3);
235 assert_eq!(r.as_usize_range(), 2..5);
236 }
237
238 #[test]
239 fn an_empty_range_is_empty() {
240 let r = IdxRange::empty_at(Idx::<Inst>::new(9));
241 assert!(r.is_empty());
242 assert_eq!(r.iter().count(), 0);
243 }
244
245 #[test]
246 #[should_panic(expected = "reversed index range")]
247 fn a_reversed_range_is_rejected() {
248 let _ = IdxRange::new(Idx::<Inst>::new(5), Idx::<Inst>::new(2));
249 }
250
251 #[test]
252 #[should_panic(expected = "index out of range")]
253 fn the_niche_value_is_rejected() {
254 let _ = Idx::<Inst>::new(u32::MAX);
255 }
256}