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_inner(self) -> IndexType {
72                self.0
73            }
74        }
75
76        impl<IndexType> $optional_index<IndexType> {
77            #[allow(dead_code)]
78            pub fn new_some(value: IndexType) -> Self
79            where
80                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug,
81            {
82                assert_ne!(value, IndexType::max_value());
83                Self(value)
84            }
85
86            #[allow(dead_code)]
87            pub fn new_none() -> Self
88            where
89                IndexType: num_traits::bounds::UpperBounded,
90            {
91                Self(IndexType::max_value())
92            }
93
94            #[allow(dead_code)]
95            pub fn from_usize(value: usize) -> Self
96            where
97                IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug + TryFrom<usize>,
98            {
99                Self::new_some(
100                    value
101                        .try_into()
102                        .ok()
103                        .expect("index conversion from usize failed"),
104                )
105            }
106
107            #[allow(dead_code)]
108            pub fn into_inner(self) -> Option<IndexType>
109            where
110                IndexType: num_traits::bounds::UpperBounded + Eq,
111            {
112                if self.is_some() { Some(self.0) } else { None }
113            }
114
115            #[allow(dead_code)]
116            pub fn is_some(&self) -> bool
117            where
118                IndexType: num_traits::bounds::UpperBounded + Eq,
119            {
120                self.0 != IndexType::max_value()
121            }
122
123            #[allow(dead_code)]
124            pub fn is_none(&self) -> bool
125            where
126                IndexType: num_traits::bounds::UpperBounded + Eq,
127            {
128                self.0 == IndexType::max_value()
129            }
130        }
131
132        /////////////////////////
133        ////// Conversions //////
134        /////////////////////////
135
136        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
137            From<Option<$index<IndexType>>> for $optional_index<IndexType>
138        {
139            fn from(index: Option<$index<IndexType>>) -> Self {
140                if let Some(index) = index {
141                    Self::new_some(index.0)
142                } else {
143                    Self::new_none()
144                }
145            }
146        }
147
148        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
149            From<$index<IndexType>> for $optional_index<IndexType>
150        {
151            fn from(index: $index<IndexType>) -> Self {
152                Self::new_some(index.0)
153            }
154        }
155
156        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
157            From<$optional_index<IndexType>> for Option<$index<IndexType>>
158        {
159            fn from(optional_index: $optional_index<IndexType>) -> Self {
160                if optional_index.is_some() {
161                    Some($index::new(optional_index.0))
162                } else {
163                    None
164                }
165            }
166        }
167
168        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> From<IndexType>
169            for $index<IndexType>
170        {
171            fn from(value: IndexType) -> Self {
172                Self::new(value)
173            }
174        }
175
176        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> From<IndexType>
177            for $optional_index<IndexType>
178        {
179            fn from(value: IndexType) -> Self {
180                Self::new_some(value)
181            }
182        }
183
184        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug>
185            From<Option<IndexType>> for $optional_index<IndexType>
186        {
187            fn from(value: Option<IndexType>) -> Self {
188                match value {
189                    Some(v) => Self::new_some(v),
190                    None => Self::new_none(),
191                }
192            }
193        }
194
195        ////////////////////////////////////
196        ////// Conversions into usize //////
197        ////////////////////////////////////
198
199        impl<IndexType: TryInto<usize>> From<$index<IndexType>> for usize {
200            fn from(index: $index<IndexType>) -> Self {
201                index
202                    .0
203                    .try_into()
204                    .ok()
205                    .expect("index conversion to usize failed")
206            }
207        }
208
209        impl<IndexType: num_traits::bounds::UpperBounded + Eq + TryInto<usize>>
210            From<$optional_index<IndexType>> for Option<usize>
211        {
212            fn from(index: $optional_index<IndexType>) -> Self {
213                if index.is_some() {
214                    Some(
215                        index
216                            .0
217                            .try_into()
218                            .ok()
219                            .expect("index conversion to usize failed"),
220                    )
221                } else {
222                    None
223                }
224            }
225        }
226
227        ////////////////////////
228        ////// Formatting //////
229        ////////////////////////
230
231        impl<IndexType: std::fmt::Debug> std::fmt::Debug for $index<IndexType> {
232            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
233                write!(f, "{}({:?})", stringify!($index), self.0)
234            }
235        }
236
237        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Debug> std::fmt::Debug
238            for $optional_index<IndexType>
239        {
240            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
241                if self.is_some() {
242                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
243                } else {
244                    write!(f, "{}(None)", stringify!($optional_index))
245                }
246            }
247        }
248
249        impl<IndexType: std::fmt::Display> std::fmt::Display for $index<IndexType> {
250            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251                write!(f, "{}", self.0)
252            }
253        }
254
255        impl<IndexType: num_traits::bounds::UpperBounded + Eq + std::fmt::Display> std::fmt::Display
256            for $optional_index<IndexType>
257        {
258            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
259                if self.is_some() {
260                    write!(f, "{}", self.0)
261                } else {
262                    write!(f, "None")
263                }
264            }
265        }
266
267        //////////////////////////
268        ////// Clone + Copy //////
269        //////////////////////////
270
271        impl<IndexType: Clone> Clone for $index<IndexType> {
272            fn clone(&self) -> Self {
273                Self(self.0.clone())
274            }
275        }
276
277        impl<IndexType: Clone> Clone for $optional_index<IndexType> {
278            fn clone(&self) -> Self {
279                Self(self.0.clone())
280            }
281        }
282
283        impl<IndexType: Copy> Copy for $index<IndexType> {}
284
285        impl<IndexType: Copy> Copy for $optional_index<IndexType> {}
286
287        //////////////////////
288        ////// Equality //////
289        //////////////////////
290
291        impl<IndexType: PartialEq> PartialEq for $index<IndexType> {
292            fn eq(&self, other: &Self) -> bool {
293                self.0.eq(&other.0)
294            }
295        }
296
297        impl<IndexType: PartialEq> PartialEq for $optional_index<IndexType> {
298            fn eq(&self, other: &Self) -> bool {
299                self.0.eq(&other.0)
300            }
301        }
302
303        impl<IndexType: Eq> Eq for $index<IndexType> {}
304
305        impl<IndexType: Eq> Eq for $optional_index<IndexType> {}
306
307        //////////////////////
308        ////// Ordering //////
309        //////////////////////
310
311        impl<IndexType: PartialOrd> PartialOrd for $index<IndexType> {
312            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
313                self.0.partial_cmp(&other.0)
314            }
315        }
316
317        impl<IndexType: PartialOrd> PartialOrd for $optional_index<IndexType> {
318            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
319                self.0.partial_cmp(&other.0)
320            }
321        }
322
323        impl<IndexType: Ord> Ord for $index<IndexType> {
324            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
325                self.0.cmp(&other.0)
326            }
327        }
328
329        impl<IndexType: Ord> Ord for $optional_index<IndexType> {
330            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
331                self.0.cmp(&other.0)
332            }
333        }
334
335        /////////////////////
336        ////// Hashing //////
337        /////////////////////
338
339        impl<IndexType: std::hash::Hash> std::hash::Hash for $index<IndexType> {
340            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
341                self.0.hash(state);
342            }
343        }
344
345        impl<IndexType: std::hash::Hash> std::hash::Hash for $optional_index<IndexType> {
346            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
347                self.0.hash(state);
348            }
349        }
350    };
351}
352
353#[macro_export]
354macro_rules! implement_fixed_index {
355    ($index:ident, $optional_index:ident, $index_type:ty) => {
356        struct $index($index_type);
357
358        struct $optional_index($index_type);
359
360        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
361    };
362
363    (pub $index:ident, pub $optional_index:ident, $index_type:ty) => {
364        pub struct $index($index_type);
365
366        pub struct $optional_index($index_type);
367
368        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
369    };
370
371    (pub(crate) $index:ident, pub(crate) $optional_index:ident, $index_type:ty) => {
372        pub(crate) struct $index($index_type);
373
374        pub(crate) struct $optional_index($index_type);
375
376        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
377    };
378
379    (pub(super) $index:ident, pub(super) $optional_index:ident, $index_type:ty) => {
380        pub(super) struct $index($index_type);
381
382        pub(super) struct $optional_index($index_type);
383
384        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
385    };
386
387    (pub(in $index_visibility:path) $index:ident, pub(in $optional_index_visibility:path) $optional_index:ident, $index_type:ty) => {
388        pub(in $index_visibility) struct $index($index_type);
389
390        pub(in $optional_index_visibility) struct $optional_index($index_type);
391
392        implement_fixed_index!($index, $optional_index, $index_type, __inner__);
393    };
394
395    ($index:ident, $optional_index:ident, $index_type:ty, __inner__) => {
396        impl $index {
397            #[allow(dead_code)]
398            pub fn new(value: $index_type) -> Self {
399                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
400                Self(value)
401            }
402
403            #[allow(dead_code)]
404            pub fn from_usize(value: usize) -> Self {
405                Self::new(
406                    value
407                        .try_into()
408                        .ok()
409                        .expect("index conversion from usize failed"),
410                )
411            }
412
413            #[allow(dead_code)]
414            pub fn into_inner(self) -> $index_type {
415                self.0
416            }
417        }
418
419        impl $optional_index {
420            #[allow(dead_code)]
421            pub fn new_some(value: $index_type) -> Self {
422                assert_ne!(value, num_traits::bounds::UpperBounded::max_value());
423                Self(value)
424            }
425
426            #[allow(dead_code)]
427            pub fn new_none() -> Self {
428                Self(num_traits::bounds::UpperBounded::max_value())
429            }
430
431            #[allow(dead_code)]
432            pub fn from_usize(value: usize) -> Self {
433                Self::new_some(
434                    value
435                        .try_into()
436                        .ok()
437                        .expect("index conversion from usize failed"),
438                )
439            }
440
441            #[allow(dead_code)]
442            pub fn into_inner(self) -> Option<$index_type> {
443                if self.is_some() { Some(self.0) } else { None }
444            }
445
446            #[allow(dead_code)]
447            pub fn is_some(&self) -> bool {
448                self.0 != num_traits::bounds::UpperBounded::max_value()
449            }
450
451            #[allow(dead_code)]
452            pub fn is_none(&self) -> bool {
453                self.0 == num_traits::bounds::UpperBounded::max_value()
454            }
455        }
456
457        /////////////////////////
458        ////// Conversions //////
459        /////////////////////////
460
461        impl From<Option<$index>> for $optional_index {
462            fn from(index: Option<$index>) -> Self {
463                if let Some(index) = index {
464                    Self::new_some(index.0)
465                } else {
466                    Self::new_none()
467                }
468            }
469        }
470
471        impl From<$index> for $optional_index {
472            fn from(index: $index) -> Self {
473                Self::new_some(index.0)
474            }
475        }
476
477        impl From<$optional_index> for Option<$index> {
478            fn from(optional_index: $optional_index) -> Self {
479                if optional_index.is_some() {
480                    Some($index::new(optional_index.0))
481                } else {
482                    None
483                }
484            }
485        }
486
487        impl From<$index_type> for $index {
488            fn from(value: $index_type) -> Self {
489                Self::new(value)
490            }
491        }
492
493        impl From<$index_type> for $optional_index {
494            fn from(value: $index_type) -> Self {
495                Self::new_some(value)
496            }
497        }
498
499        impl From<Option<$index_type>> for $optional_index {
500            fn from(value: Option<$index_type>) -> Self {
501                match value {
502                    Some(v) => Self::new_some(v),
503                    None => Self::new_none(),
504                }
505            }
506        }
507
508        ////////////////////////////////////
509        ////// Conversions into usize //////
510        ////////////////////////////////////
511
512        impl From<$index> for usize {
513            fn from(index: $index) -> Self {
514                index
515                    .0
516                    .try_into()
517                    .ok()
518                    .expect("index conversion to usize failed")
519            }
520        }
521
522        impl From<$optional_index> for Option<usize> {
523            fn from(index: $optional_index) -> Self {
524                if index.is_some() {
525                    Some(
526                        index
527                            .0
528                            .try_into()
529                            .ok()
530                            .expect("index conversion to usize failed"),
531                    )
532                } else {
533                    None
534                }
535            }
536        }
537
538        ////////////////////////
539        ////// Formatting //////
540        ////////////////////////
541
542        impl std::fmt::Debug for $index {
543            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544                write!(f, "{}({:?})", stringify!($index), self.0)
545            }
546        }
547
548        impl std::fmt::Debug for $optional_index {
549            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
550                if self.is_some() {
551                    write!(f, "{}({:?})", stringify!($optional_index), self.0)
552                } else {
553                    write!(f, "{}(None)", stringify!($optional_index))
554                }
555            }
556        }
557
558        impl std::fmt::Display for $index {
559            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560                write!(f, "{}", self.0)
561            }
562        }
563
564        impl std::fmt::Display for $optional_index {
565            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566                if self.is_some() {
567                    write!(f, "{}", self.0)
568                } else {
569                    write!(f, "None")
570                }
571            }
572        }
573
574        //////////////////////////
575        ////// Clone + Copy //////
576        //////////////////////////
577
578        impl Clone for $index {
579            fn clone(&self) -> Self {
580                *self
581            }
582        }
583
584        impl Clone for $optional_index {
585            fn clone(&self) -> Self {
586                *self
587            }
588        }
589
590        impl Copy for $index {}
591
592        impl Copy for $optional_index {}
593
594        //////////////////////
595        ////// Equality //////
596        //////////////////////
597
598        impl PartialEq for $index {
599            fn eq(&self, other: &Self) -> bool {
600                self.0.eq(&other.0)
601            }
602        }
603
604        impl PartialEq for $optional_index {
605            fn eq(&self, other: &Self) -> bool {
606                self.0.eq(&other.0)
607            }
608        }
609
610        impl Eq for $index {}
611
612        impl Eq for $optional_index {}
613
614        //////////////////////
615        ////// Ordering //////
616        //////////////////////
617
618        impl PartialOrd for $index {
619            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
620                Some(self.cmp(other))
621            }
622        }
623
624        impl PartialOrd for $optional_index {
625            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
626                Some(self.cmp(other))
627            }
628        }
629
630        impl Ord for $index {
631            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
632                self.0.cmp(&other.0)
633            }
634        }
635
636        impl Ord for $optional_index {
637            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
638                self.0.cmp(&other.0)
639            }
640        }
641
642        /////////////////////
643        ////// Hashing //////
644        /////////////////////
645
646        impl std::hash::Hash for $index {
647            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
648                self.0.hash(state);
649            }
650        }
651
652        impl std::hash::Hash for $optional_index {
653            fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
654                self.0.hash(state);
655            }
656        }
657    };
658}