Skip to main content

optional_numeric_index/
lib.rs

1#[cfg(test)]
2mod tests;
3
4#[macro_export]
5macro_rules! implement_generic_index {
6    ($index:ident, $optional_index:ident) => {
7        pub struct $index<IndexType>(IndexType);
8
9        pub struct $optional_index<IndexType>(IndexType);
10
11        impl<IndexType> $index<IndexType> {
12            pub fn new(value: IndexType) -> Self
13            where
14                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
15            {
16                assert_ne!(value, IndexType::max_value());
17                Self(value)
18            }
19
20            pub fn from_usize(value: usize) -> Self
21            where
22                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
23            {
24                Self::new(
25                    value
26                        .try_into()
27                        .ok()
28                        .expect("index conversion from usize failed"),
29                )
30            }
31
32            pub fn into_inner(self) -> IndexType {
33                self.0
34            }
35        }
36
37        impl<IndexType> $optional_index<IndexType> {
38            pub fn new_some(value: IndexType) -> Self
39            where
40                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
41            {
42                assert_ne!(value, IndexType::max_value());
43                Self(value)
44            }
45
46            pub fn new_none() -> Self
47            where
48                IndexType: num_traits::bounds::UpperBounded,
49            {
50                Self(IndexType::max_value())
51            }
52
53            pub fn from_usize(value: usize) -> Self
54            where
55                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
56            {
57                Self::new_some(
58                    value
59                        .try_into()
60                        .ok()
61                        .expect("index conversion from usize failed"),
62                )
63            }
64
65            pub fn into_inner(self) -> Option<IndexType>
66            where
67                IndexType: num_traits::bounds::UpperBounded + Eq,
68            {
69                if self.is_some() { Some(self.0) } else { None }
70            }
71
72            pub fn is_some(&self) -> bool
73            where
74                IndexType: num_traits::bounds::UpperBounded + Eq,
75            {
76                self.0 != IndexType::max_value()
77            }
78
79            pub fn is_none(&self) -> bool
80            where
81                IndexType: num_traits::bounds::UpperBounded + Eq,
82            {
83                self.0 == IndexType::max_value()
84            }
85        }
86
87        /////////////////////////
88        ////// Conversions //////
89        /////////////////////////
90
91        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
92            From<Option<$index<IndexType>>> for $optional_index<IndexType>
93        {
94            fn from(index: Option<$index<IndexType>>) -> Self {
95                if let Some(index) = index {
96                    Self::new_some(index.0)
97                } else {
98                    Self::new_none()
99                }
100            }
101        }
102
103        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
104            From<$index<IndexType>> for $optional_index<IndexType>
105        {
106            fn from(index: $index<IndexType>) -> Self {
107                Self::new_some(index.0)
108            }
109        }
110
111        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
112            From<$optional_index<IndexType>> for Option<$index<IndexType>>
113        {
114            fn from(optional_index: $optional_index<IndexType>) -> Self {
115                if optional_index.is_some() {
116                    Some($index::new(optional_index.0))
117                } else {
118                    None
119                }
120            }
121        }
122
123        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> From<IndexType>
124            for $index<IndexType>
125        {
126            fn from(value: IndexType) -> Self {
127                Self::new(value)
128            }
129        }
130
131        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> From<IndexType>
132            for $optional_index<IndexType>
133        {
134            fn from(value: IndexType) -> Self {
135                Self::new_some(value)
136            }
137        }
138
139        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
140            From<Option<IndexType>> for $optional_index<IndexType>
141        {
142            fn from(value: Option<IndexType>) -> Self {
143                match value {
144                    Some(v) => Self::new_some(v),
145                    None => Self::new_none(),
146                }
147            }
148        }
149
150        ////////////////////////////////////
151        ////// Conversions into usize //////
152        ////////////////////////////////////
153
154        impl<IndexType: TryInto<usize>> From<$index<IndexType>> for usize {
155            fn from(index: $index<IndexType>) -> Self {
156                index
157                    .0
158                    .try_into()
159                    .ok()
160                    .expect("index conversion to usize failed")
161            }
162        }
163
164        impl<IndexType: num_traits::bounds::UpperBounded + Eq + TryInto<usize>>
165            From<$optional_index<IndexType>> for Option<usize>
166        {
167            fn from(index: $optional_index<IndexType>) -> Self {
168                if index.is_some() {
169                    Some(
170                        index
171                            .0
172                            .try_into()
173                            .ok()
174                            .expect("index conversion to usize failed"),
175                    )
176                } else {
177                    None
178                }
179            }
180        }
181
182        ////////////////////////
183        ////// Formatting //////
184        ////////////////////////
185
186        impl<IndexType: std::fmt::Debug> std::fmt::Debug for $index<IndexType> {
187            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188                write!(f, "{}({:?})", stringify!($index), self.0)
189            }
190        }
191
192        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> std::fmt::Debug
193            for $optional_index<IndexType>
194        {
195            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196                if self.is_some() {
197                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
198                } else {
199                    write!(f, "{}(None)", stringify!($optional_index))
200                }
201            }
202        }
203
204        impl<IndexType: std::fmt::Display> std::fmt::Display for $index<IndexType> {
205            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206                write!(f, "{}", self.0)
207            }
208        }
209
210        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Display> std::fmt::Display
211            for $optional_index<IndexType>
212        {
213            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214                if self.is_some() {
215                    write!(f, "{}", self.0)
216                } else {
217                    write!(f, "None")
218                }
219            }
220        }
221
222        //////////////////////////
223        ////// Clone + Copy //////
224        //////////////////////////
225
226        impl<IndexType: Clone> Clone for $index<IndexType> {
227            fn clone(&self) -> Self {
228                Self(self.0.clone())
229            }
230        }
231
232        impl<IndexType: Clone> Clone for $optional_index<IndexType> {
233            fn clone(&self) -> Self {
234                Self(self.0.clone())
235            }
236        }
237
238        impl<IndexType: Copy> Copy for $index<IndexType> {}
239
240        impl<IndexType: Copy> Copy for $optional_index<IndexType> {}
241
242        //////////////////////
243        ////// Equality //////
244        //////////////////////
245
246        impl<IndexType: PartialEq> PartialEq for $index<IndexType> {
247            fn eq(&self, other: &Self) -> bool {
248                self.0.eq(&other.0)
249            }
250        }
251
252        impl<IndexType: PartialEq> PartialEq for $optional_index<IndexType> {
253            fn eq(&self, other: &Self) -> bool {
254                self.0.eq(&other.0)
255            }
256        }
257
258        impl<IndexType: Eq> Eq for $index<IndexType> {}
259
260        impl<IndexType: Eq> Eq for $optional_index<IndexType> {}
261
262        //////////////////////
263        ////// Ordering //////
264        //////////////////////
265
266        impl<IndexType: PartialOrd> PartialOrd for $index<IndexType> {
267            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
268                self.0.partial_cmp(&other.0)
269            }
270        }
271
272        impl<IndexType: PartialOrd> PartialOrd for $optional_index<IndexType> {
273            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
274                self.0.partial_cmp(&other.0)
275            }
276        }
277
278        impl<IndexType: Ord> Ord for $index<IndexType> {
279            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
280                self.0.cmp(&other.0)
281            }
282        }
283
284        impl<IndexType: Ord> Ord for $optional_index<IndexType> {
285            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
286                self.0.cmp(&other.0)
287            }
288        }
289
290        /////////////////////
291        ////// Hashing //////
292        /////////////////////
293
294        impl<IndexType: std::hash::Hash> std::hash::Hash for $index<IndexType> {
295            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
296                self.0.hash(state);
297            }
298        }
299
300        impl<IndexType: std::hash::Hash> std::hash::Hash for $optional_index<IndexType> {
301            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
302                self.0.hash(state);
303            }
304        }
305    };
306}
307
308#[macro_export]
309macro_rules! implement_fixed_index {
310    ($index:ident, $optional_index:ident, $index_type:ty) => {
311        pub struct $index($index_type);
312
313        pub struct $optional_index($index_type);
314
315        impl $index {
316            pub fn new(value: $index_type) -> Self {
317                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
318                Self(value)
319            }
320
321            pub fn from_usize(value: usize) -> Self {
322                Self::new(
323                    value
324                        .try_into()
325                        .ok()
326                        .expect("index conversion from usize failed"),
327                )
328            }
329
330            pub fn into_inner(self) -> $index_type {
331                self.0
332            }
333        }
334
335        impl $optional_index {
336            pub fn new_some(value: $index_type) -> Self {
337                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
338                Self(value)
339            }
340
341            pub fn new_none() -> Self {
342                Self(num_traits::bounds::UpperBounded::max_value())
343            }
344
345            pub fn from_usize(value: usize) -> Self {
346                Self::new_some(
347                    value
348                        .try_into()
349                        .ok()
350                        .expect("index conversion from usize failed"),
351                )
352            }
353
354            pub fn into_inner(self) -> Option<$index_type> {
355                if self.is_some() { Some(self.0) } else { None }
356            }
357
358            pub fn is_some(&self) -> bool {
359                self.0 != num_traits::bounds::UpperBounded::max_value()
360            }
361
362            pub fn is_none(&self) -> bool {
363                self.0 == num_traits::bounds::UpperBounded::max_value()
364            }
365        }
366
367        /////////////////////////
368        ////// Conversions //////
369        /////////////////////////
370
371        impl From<Option<$index>> for $optional_index {
372            fn from(index: Option<$index>) -> Self {
373                if let Some(index) = index {
374                    Self::new_some(index.0)
375                } else {
376                    Self::new_none()
377                }
378            }
379        }
380
381        impl From<$index> for $optional_index {
382            fn from(index: $index) -> Self {
383                Self::new_some(index.0)
384            }
385        }
386
387        impl From<$optional_index> for Option<$index> {
388            fn from(optional_index: $optional_index) -> Self {
389                if optional_index.is_some() {
390                    Some($index::new(optional_index.0))
391                } else {
392                    None
393                }
394            }
395        }
396
397        impl From<$index_type> for $index {
398            fn from(value: $index_type) -> Self {
399                Self::new(value)
400            }
401        }
402
403        impl From<$index_type> for $optional_index {
404            fn from(value: $index_type) -> Self {
405                Self::new_some(value)
406            }
407        }
408
409        impl From<Option<$index_type>> for $optional_index {
410            fn from(value: Option<$index_type>) -> Self {
411                match value {
412                    Some(v) => Self::new_some(v),
413                    None => Self::new_none(),
414                }
415            }
416        }
417
418        ////////////////////////////////////
419        ////// Conversions into usize //////
420        ////////////////////////////////////
421
422        impl From<$index> for usize {
423            fn from(index: $index) -> Self {
424                index
425                    .0
426                    .try_into()
427                    .ok()
428                    .expect("index conversion to usize failed")
429            }
430        }
431
432        impl From<$optional_index> for Option<usize> {
433            fn from(index: $optional_index) -> Self {
434                if index.is_some() {
435                    Some(
436                        index
437                            .0
438                            .try_into()
439                            .ok()
440                            .expect("index conversion to usize failed"),
441                    )
442                } else {
443                    None
444                }
445            }
446        }
447
448        ////////////////////////
449        ////// Formatting //////
450        ////////////////////////
451
452        impl std::fmt::Debug for $index {
453            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454                write!(f, "{}({:?})", stringify!($index), self.0)
455            }
456        }
457
458        impl std::fmt::Debug for $optional_index {
459            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460                if self.is_some() {
461                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
462                } else {
463                    write!(f, "{}(None)", stringify!($optional_index))
464                }
465            }
466        }
467
468        impl std::fmt::Display for $index {
469            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
470                write!(f, "{}", self.0)
471            }
472        }
473
474        impl std::fmt::Display for $optional_index {
475            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476                if self.is_some() {
477                    write!(f, "{}", self.0)
478                } else {
479                    write!(f, "None")
480                }
481            }
482        }
483
484        //////////////////////////
485        ////// Clone + Copy //////
486        //////////////////////////
487
488        impl Clone for $index {
489            fn clone(&self) -> Self {
490                Self(self.0.clone())
491            }
492        }
493
494        impl Clone for $optional_index {
495            fn clone(&self) -> Self {
496                Self(self.0.clone())
497            }
498        }
499
500        impl Copy for $index {}
501
502        impl Copy for $optional_index {}
503
504        //////////////////////
505        ////// Equality //////
506        //////////////////////
507
508        impl PartialEq for $index {
509            fn eq(&self, other: &Self) -> bool {
510                self.0.eq(&other.0)
511            }
512        }
513
514        impl PartialEq for $optional_index {
515            fn eq(&self, other: &Self) -> bool {
516                self.0.eq(&other.0)
517            }
518        }
519
520        impl Eq for $index {}
521
522        impl Eq for $optional_index {}
523
524        //////////////////////
525        ////// Ordering //////
526        //////////////////////
527
528        impl PartialOrd for $index {
529            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
530                self.0.partial_cmp(&other.0)
531            }
532        }
533
534        impl PartialOrd for $optional_index {
535            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
536                self.0.partial_cmp(&other.0)
537            }
538        }
539
540        impl Ord for $index {
541            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
542                self.0.cmp(&other.0)
543            }
544        }
545
546        impl Ord for $optional_index {
547            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
548                self.0.cmp(&other.0)
549            }
550        }
551
552        /////////////////////
553        ////// Hashing //////
554        /////////////////////
555
556        impl std::hash::Hash for $index {
557            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
558                self.0.hash(state);
559            }
560        }
561
562        impl std::hash::Hash for $optional_index {
563            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
564                self.0.hash(state);
565            }
566        }
567    };
568}