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