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        struct $index<IndexType>(IndexType);
8
9        struct $optional_index<IndexType>(IndexType);
10
11        implement_generic_index!($index, $optional_index, __inner__);
12    };
13
14    (pub $index:ident, pub $optional_index:ident) => {
15        pub struct $index<IndexType>(IndexType);
16
17        pub struct $optional_index<IndexType>(IndexType);
18
19        implement_generic_index!($index, $optional_index, __inner__);
20    };
21
22    (pub(crate) $index:ident, pub(crate) $optional_index:ident) => {
23        pub(crate) struct $index<IndexType>(IndexType);
24
25        pub(crate) struct $optional_index<IndexType>(IndexType);
26
27        implement_generic_index!($index, $optional_index, __inner__);
28    };
29
30    (pub(super) $index:ident, pub(super) $optional_index:ident) => {
31        pub(super) struct $index<IndexType>(IndexType);
32
33        pub(super) struct $optional_index<IndexType>(IndexType);
34
35        implement_generic_index!($index, $optional_index, __inner__);
36    };
37
38    (pub(in $index_visibility:path) $index:ident, pub(in $optional_index_visibility:path) $optional_index:ident) => {
39        pub(in $index_visibility) struct $index<IndexType>(IndexType);
40
41        pub(in $optional_index_visibility) struct $optional_index<IndexType>(IndexType);
42
43        implement_generic_index!($index, $optional_index, __inner__);
44    };
45
46    ($index:ident, $optional_index:ident, __inner__) => {
47        impl<IndexType> $index<IndexType> {
48            #[allow(dead_code)]
49            pub fn new(value: IndexType) -> Self
50            where
51                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
52            {
53                assert_ne!(value, IndexType::max_value());
54                Self(value)
55            }
56
57            #[allow(dead_code)]
58            pub fn from_usize(value: usize) -> Self
59            where
60                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
61            {
62                Self::new(
63                    value
64                        .try_into()
65                        .ok()
66                        .expect("index conversion from usize failed"),
67                )
68            }
69
70            #[allow(dead_code)]
71            pub fn into_usize(self) -> usize
72            where
73                IndexType: TryInto<usize>,
74            {
75                self.0
76                    .try_into()
77                    .ok()
78                    .expect("index conversion to usize failed")
79            }
80
81            #[allow(dead_code)]
82            pub fn from_raw(value: IndexType) -> Self
83            where
84                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
85            {
86                Self::new(value)
87            }
88
89            #[allow(dead_code)]
90            pub fn into_raw(self) -> IndexType {
91                self.0
92            }
93        }
94
95        impl<IndexType> $optional_index<IndexType> {
96            #[allow(dead_code)]
97            pub fn new_some(value: IndexType) -> Self
98            where
99                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
100            {
101                assert_ne!(value, IndexType::max_value());
102                Self(value)
103            }
104
105            #[allow(dead_code)]
106            pub fn new_none() -> Self
107            where
108                IndexType: num_traits::bounds::UpperBounded,
109            {
110                Self(IndexType::max_value())
111            }
112
113            #[allow(dead_code)]
114            pub fn from_usize(value: usize) -> Self
115            where
116                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
117            {
118                Self::new_some(
119                    value
120                        .try_into()
121                        .ok()
122                        .expect("index conversion from usize failed"),
123                )
124            }
125
126            #[allow(dead_code)]
127            pub fn from_option_usize(value: Option<usize>) -> Self
128            where
129                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
130            {
131                if let Some(value) = value {
132                    Self::new_some(
133                        value
134                            .try_into()
135                            .ok()
136                            .expect("index conversion from usize failed"),
137                    )
138                } else {
139                    Self::new_none()
140                }
141            }
142
143            #[allow(dead_code)]
144            pub fn into_usize(self) -> Option<usize>
145            where
146                IndexType: num_traits::bounds::UpperBounded + Eq + TryInto<usize>,
147            {
148                if self.is_some() {
149                    Some(
150                        self.0
151                            .try_into()
152                            .ok()
153                            .expect("index conversion to usize failed"),
154                    )
155                } else {
156                    None
157                }
158            }
159
160            #[allow(dead_code)]
161            pub fn from_raw(value: Option<IndexType>) -> Self
162            where
163                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
164            {
165                if let Some(value) = value {
166                    Self::new_some(value)
167                } else {
168                    Self::new_none()
169                }
170            }
171
172            #[allow(dead_code)]
173            pub fn into_raw(self) -> Option<IndexType>
174            where
175                IndexType: num_traits::bounds::UpperBounded + Eq,
176            {
177                if self.is_some() { Some(self.0) } else { None }
178            }
179
180            #[allow(dead_code)]
181            pub fn is_some(&self) -> bool
182            where
183                IndexType: num_traits::bounds::UpperBounded + Eq,
184            {
185                self.0 != IndexType::max_value()
186            }
187
188            #[allow(dead_code)]
189            pub fn is_none(&self) -> bool
190            where
191                IndexType: num_traits::bounds::UpperBounded + Eq,
192            {
193                self.0 == IndexType::max_value()
194            }
195        }
196
197        /////////////////////////
198        ////// Conversions //////
199        /////////////////////////
200
201        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
202            From<Option<$index<IndexType>>> for $optional_index<IndexType>
203        {
204            fn from(index: Option<$index<IndexType>>) -> Self {
205                if let Some(index) = index {
206                    Self::new_some(index.0)
207                } else {
208                    Self::new_none()
209                }
210            }
211        }
212
213        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
214            From<$index<IndexType>> for $optional_index<IndexType>
215        {
216            fn from(index: $index<IndexType>) -> Self {
217                Self::new_some(index.0)
218            }
219        }
220
221        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
222            From<$optional_index<IndexType>> for Option<$index<IndexType>>
223        {
224            fn from(optional_index: $optional_index<IndexType>) -> Self {
225                if optional_index.is_some() {
226                    Some($index::new(optional_index.0))
227                } else {
228                    None
229                }
230            }
231        }
232
233        ////////////////////////////////////
234        ////// Conversions with usize //////
235        ////////////////////////////////////
236
237        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>>
238            From<usize> for $index<IndexType>
239        {
240            fn from(value: usize) -> Self {
241                Self::new(
242                    value
243                        .try_into()
244                        .ok()
245                        .expect("index conversion from usize failed"),
246                )
247            }
248        }
249
250        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>>
251            From<usize> for $optional_index<IndexType>
252        {
253            fn from(value: usize) -> Self {
254                Self::new_some(
255                    value
256                        .try_into()
257                        .ok()
258                        .expect("index conversion from usize failed"),
259                )
260            }
261        }
262
263        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>>
264            From<Option<usize>> for $optional_index<IndexType>
265        {
266            fn from(value: Option<usize>) -> Self {
267                if let Some(value) = value {
268                    Self::new_some(
269                        value
270                            .try_into()
271                            .ok()
272                            .expect("index conversion from usize failed"),
273                    )
274                } else {
275                    Self::new_none()
276                }
277            }
278        }
279
280        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryInto<usize>>
281            From<$index<IndexType>> for usize
282        {
283            fn from(value: $index<IndexType>) -> Self {
284                value
285                    .0
286                    .try_into()
287                    .ok()
288                    .expect("index conversion from usize failed")
289            }
290        }
291
292        impl<IndexType: num_traits::bounds::UpperBounded + Eq + TryInto<usize>>
293            From<$optional_index<IndexType>> for Option<usize>
294        {
295            fn from(index: $optional_index<IndexType>) -> Self {
296                if index.is_some() {
297                    Some(
298                        index
299                            .0
300                            .try_into()
301                            .ok()
302                            .expect("index conversion to usize failed"),
303                    )
304                } else {
305                    None
306                }
307            }
308        }
309
310        ////////////////////////
311        ////// Formatting //////
312        ////////////////////////
313
314        impl<IndexType: std::fmt::Debug> std::fmt::Debug for $index<IndexType> {
315            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316                write!(f, "{}({:?})", stringify!($index), self.0)
317            }
318        }
319
320        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> std::fmt::Debug
321            for $optional_index<IndexType>
322        {
323            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324                if self.is_some() {
325                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
326                } else {
327                    write!(f, "{}(None)", stringify!($optional_index))
328                }
329            }
330        }
331
332        impl<IndexType: std::fmt::Display> std::fmt::Display for $index<IndexType> {
333            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334                write!(f, "{}", self.0)
335            }
336        }
337
338        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Display> std::fmt::Display
339            for $optional_index<IndexType>
340        {
341            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342                if self.is_some() {
343                    write!(f, "{}", self.0)
344                } else {
345                    write!(f, "None")
346                }
347            }
348        }
349
350        //////////////////////////
351        ////// Clone + Copy //////
352        //////////////////////////
353
354        impl<IndexType: Clone> Clone for $index<IndexType> {
355            fn clone(&self) -> Self {
356                Self(self.0.clone())
357            }
358        }
359
360        impl<IndexType: Clone> Clone for $optional_index<IndexType> {
361            fn clone(&self) -> Self {
362                Self(self.0.clone())
363            }
364        }
365
366        impl<IndexType: Copy> Copy for $index<IndexType> {}
367
368        impl<IndexType: Copy> Copy for $optional_index<IndexType> {}
369
370        //////////////////////
371        ////// Equality //////
372        //////////////////////
373
374        impl<IndexType: PartialEq> PartialEq for $index<IndexType> {
375            fn eq(&self, other: &Self) -> bool {
376                self.0.eq(&other.0)
377            }
378        }
379
380        impl<IndexType: PartialEq> PartialEq for $optional_index<IndexType> {
381            fn eq(&self, other: &Self) -> bool {
382                self.0.eq(&other.0)
383            }
384        }
385
386        impl<IndexType: Eq> Eq for $index<IndexType> {}
387
388        impl<IndexType: Eq> Eq for $optional_index<IndexType> {}
389
390        //////////////////////
391        ////// Ordering //////
392        //////////////////////
393
394        impl<IndexType: PartialOrd> PartialOrd for $index<IndexType> {
395            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
396                self.0.partial_cmp(&other.0)
397            }
398        }
399
400        impl<IndexType: PartialOrd> PartialOrd for $optional_index<IndexType> {
401            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
402                self.0.partial_cmp(&other.0)
403            }
404        }
405
406        impl<IndexType: Ord> Ord for $index<IndexType> {
407            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
408                self.0.cmp(&other.0)
409            }
410        }
411
412        impl<IndexType: Ord> Ord for $optional_index<IndexType> {
413            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
414                self.0.cmp(&other.0)
415            }
416        }
417
418        /////////////////////
419        ////// Hashing //////
420        /////////////////////
421
422        impl<IndexType: std::hash::Hash> std::hash::Hash for $index<IndexType> {
423            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
424                self.0.hash(state);
425            }
426        }
427
428        impl<IndexType: std::hash::Hash> std::hash::Hash for $optional_index<IndexType> {
429            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
430                self.0.hash(state);
431            }
432        }
433    };
434}
435
436#[macro_export]
437macro_rules! implement_fixed_index {
438    ($index:ident, $optional_index:ident, $index_type:ty) => {
439        struct $index($index_type);
440
441        struct $optional_index($index_type);
442
443        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
444    };
445
446    (pub $index:ident, pub $optional_index:ident, $index_type:ty) => {
447        pub struct $index($index_type);
448
449        pub struct $optional_index($index_type);
450
451        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
452    };
453
454    (pub(crate) $index:ident, pub(crate) $optional_index:ident, $index_type:ty) => {
455        pub(crate) struct $index($index_type);
456
457        pub(crate) struct $optional_index($index_type);
458
459        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
460    };
461
462    (pub(super) $index:ident, pub(super) $optional_index:ident, $index_type:ty) => {
463        pub(super) struct $index($index_type);
464
465        pub(super) struct $optional_index($index_type);
466
467        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
468    };
469
470    (pub(in $index_visibility:path) $index:ident, pub(in $optional_index_visibility:path) $optional_index:ident, $index_type:ty) => {
471        pub(in $index_visibility) struct $index($index_type);
472
473        pub(in $optional_index_visibility) struct $optional_index($index_type);
474
475        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
476    };
477
478    ($index:ident, $optional_index:ident, $index_type:ty, __inner__) => {
479        impl $index {
480            #[allow(dead_code)]
481            pub fn new(value: $index_type) -> Self {
482                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
483                Self(value)
484            }
485
486            #[allow(dead_code)]
487            pub fn from_usize(value: usize) -> Self {
488                Self::new(
489                    value
490                        .try_into()
491                        .ok()
492                        .expect("index conversion from usize failed"),
493                )
494            }
495
496            #[allow(dead_code)]
497            pub fn into_usize(self) -> usize {
498                self.0
499                    .try_into()
500                    .ok()
501                    .expect("index conversion to usize failed")
502            }
503
504            #[allow(dead_code)]
505            pub fn from_raw(value: $index_type) -> Self {
506                Self::new(value)
507            }
508
509            #[allow(dead_code)]
510            pub fn into_raw(self) -> $index_type {
511                self.0
512            }
513        }
514
515        impl $optional_index {
516            #[allow(dead_code)]
517            pub fn new_some(value: $index_type) -> Self {
518                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
519                Self(value)
520            }
521
522            #[allow(dead_code)]
523            pub fn new_none() -> Self {
524                Self(num_traits::bounds::UpperBounded::max_value())
525            }
526
527            #[allow(dead_code)]
528            pub fn from_usize(value: usize) -> Self {
529                Self::new_some(
530                    value
531                        .try_into()
532                        .ok()
533                        .expect("index conversion from usize failed"),
534                )
535            }
536
537            #[allow(dead_code)]
538            pub fn from_option_usize(value: Option<usize>) -> Self {
539                if let Some(value) = value {
540                    Self::new_some(
541                        value
542                            .try_into()
543                            .ok()
544                            .expect("index conversion from usize failed"),
545                    )
546                } else {
547                    Self::new_none()
548                }
549            }
550
551            #[allow(dead_code)]
552            pub fn into_usize(self) -> Option<usize> {
553                if self.is_some() {
554                    Some(
555                        self.0
556                            .try_into()
557                            .ok()
558                            .expect("index conversion to usize failed"),
559                    )
560                } else {
561                    None
562                }
563            }
564
565            #[allow(dead_code)]
566            pub fn from_raw(value: Option<$index_type>) -> Self {
567                if let Some(value) = value {
568                    Self::new_some(value)
569                } else {
570                    Self::new_none()
571                }
572            }
573
574            #[allow(dead_code)]
575            pub fn into_raw(self) -> Option<$index_type> {
576                if self.is_some() { Some(self.0) } else { None }
577            }
578
579            #[allow(dead_code)]
580            pub fn is_some(&self) -> bool {
581                self.0 != num_traits::bounds::UpperBounded::max_value()
582            }
583
584            #[allow(dead_code)]
585            pub fn is_none(&self) -> bool {
586                self.0 == num_traits::bounds::UpperBounded::max_value()
587            }
588        }
589
590        /////////////////////////
591        ////// Conversions //////
592        /////////////////////////
593
594        impl From<Option<$index>> for $optional_index {
595            fn from(index: Option<$index>) -> Self {
596                if let Some(index) = index {
597                    Self::new_some(index.0)
598                } else {
599                    Self::new_none()
600                }
601            }
602        }
603
604        impl From<$index> for $optional_index {
605            fn from(index: $index) -> Self {
606                Self::new_some(index.0)
607            }
608        }
609
610        impl From<$optional_index> for Option<$index> {
611            fn from(optional_index: $optional_index) -> Self {
612                if optional_index.is_some() {
613                    Some($index::new(optional_index.0))
614                } else {
615                    None
616                }
617            }
618        }
619
620        ////////////////////////////////////
621        ////// Conversions with usize //////
622        ////////////////////////////////////
623
624        impl From<usize> for $index {
625            fn from(value: usize) -> Self {
626                Self::new(
627                    value
628                        .try_into()
629                        .ok()
630                        .expect("index conversion from usize failed"),
631                )
632            }
633        }
634
635        impl From<usize> for $optional_index {
636            fn from(value: usize) -> Self {
637                Self::new_some(
638                    value
639                        .try_into()
640                        .ok()
641                        .expect("index conversion from usize failed"),
642                )
643            }
644        }
645
646        impl From<Option<usize>> for $optional_index {
647            fn from(value: Option<usize>) -> Self {
648                if let Some(value) = value {
649                    Self::new_some(
650                        value
651                            .try_into()
652                            .ok()
653                            .expect("index conversion from usize failed"),
654                    )
655                } else {
656                    Self::new_none()
657                }
658            }
659        }
660
661        impl From<$index> for usize {
662            fn from(index: $index) -> Self {
663                index
664                    .0
665                    .try_into()
666                    .ok()
667                    .expect("index conversion to usize failed")
668            }
669        }
670
671        impl From<$optional_index> for Option<usize> {
672            fn from(index: $optional_index) -> Self {
673                if index.is_some() {
674                    Some(
675                        index
676                            .0
677                            .try_into()
678                            .ok()
679                            .expect("index conversion to usize failed"),
680                    )
681                } else {
682                    None
683                }
684            }
685        }
686
687        ////////////////////////
688        ////// Formatting //////
689        ////////////////////////
690
691        impl std::fmt::Debug for $index {
692            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693                write!(f, "{}({:?})", stringify!($index), self.0)
694            }
695        }
696
697        impl std::fmt::Debug for $optional_index {
698            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699                if self.is_some() {
700                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
701                } else {
702                    write!(f, "{}(None)", stringify!($optional_index))
703                }
704            }
705        }
706
707        impl std::fmt::Display for $index {
708            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709                write!(f, "{}", self.0)
710            }
711        }
712
713        impl std::fmt::Display for $optional_index {
714            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
715                if self.is_some() {
716                    write!(f, "{}", self.0)
717                } else {
718                    write!(f, "None")
719                }
720            }
721        }
722
723        //////////////////////////
724        ////// Clone + Copy //////
725        //////////////////////////
726
727        impl Clone for $index {
728            fn clone(&self) -> Self {
729                *self
730            }
731        }
732
733        impl Clone for $optional_index {
734            fn clone(&self) -> Self {
735                *self
736            }
737        }
738
739        impl Copy for $index {}
740
741        impl Copy for $optional_index {}
742
743        //////////////////////
744        ////// Equality //////
745        //////////////////////
746
747        impl PartialEq for $index {
748            fn eq(&self, other: &Self) -> bool {
749                self.0.eq(&other.0)
750            }
751        }
752
753        impl PartialEq for $optional_index {
754            fn eq(&self, other: &Self) -> bool {
755                self.0.eq(&other.0)
756            }
757        }
758
759        impl Eq for $index {}
760
761        impl Eq for $optional_index {}
762
763        //////////////////////
764        ////// Ordering //////
765        //////////////////////
766
767        impl PartialOrd for $index {
768            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
769                Some(self.cmp(other))
770            }
771        }
772
773        impl PartialOrd for $optional_index {
774            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
775                Some(self.cmp(other))
776            }
777        }
778
779        impl Ord for $index {
780            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
781                self.0.cmp(&other.0)
782            }
783        }
784
785        impl Ord for $optional_index {
786            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
787                self.0.cmp(&other.0)
788            }
789        }
790
791        /////////////////////
792        ////// Hashing //////
793        /////////////////////
794
795        impl std::hash::Hash for $index {
796            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
797                self.0.hash(state);
798            }
799        }
800
801        impl std::hash::Hash for $optional_index {
802            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
803                self.0.hash(state);
804            }
805        }
806    };
807}