one_based/
lib.rs

1//! Provides OneBased* unsigned int types, which wraps several integers as 1-based index.
2//!
3//! Example:
4//! ```
5//! # use one_based::{OneBasedU32, OneBasedU64};
6//! # use std::num::NonZeroU64;
7//! // constructs from 1-based.
8//! let v = OneBasedU32::from_one_based(1).unwrap();
9//! assert_eq!(v.as_zero_based(), 0);
10//!
11//! // constructs from 0-based.
12//! let v = OneBasedU64::from_zero_based(0).unwrap();
13//! assert_eq!(v.as_one_based(), NonZeroU64::new(1).unwrap());
14//!
15//! // fails to construct from zero as one-based.
16//! let v: Option<OneBasedU32> = OneBasedU32::from_one_based(0);
17//! assert_eq!(v, None);
18//!
19//! // fails to construct from max as zero-based.
20//! let v: Option<OneBasedU32> = OneBasedU32::from_zero_based(u32::MAX);
21//! assert_eq!(v, None);
22//!
23//! // string format uses 1-based.
24//! let v: OneBasedU32 = "5".parse().unwrap();
25//! assert_eq!(v.as_zero_based(), 4);
26//! assert_eq!(v.to_string(), "5");
27//! ```
28
29#![no_std]
30
31use core::{
32    fmt::Display,
33    num::{
34        NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, ParseIntError,
35    },
36    str::FromStr,
37};
38
39trait OneBased {
40    type IntType;
41    type NonZeroType;
42}
43
44macro_rules! define_one_based {
45    ($name:ident, $itype:ty, $nonzerotype:ty) => {
46        #[doc = concat!(r" Represents 1-based index of ", stringify!($itype), r".")]
47        ///
48        /// To describe configuration by humans, often 1-based index is easier than 0-based to understand.
49        /// On the other hand, 0-based index is easier to use in the programming.
50        /// Also, it's quite hard to track if the index is 0-based or 1-based.
51        /// `$name` provides ergonomics to handle user provided 1-baed index safely.
52        ///
53        /// ```
54        #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
55        #[doc = r" // Creates from 1-based index"]
56        #[doc = concat!(r" let v = ", stringify!($name),r"::from_one_based(5).unwrap();")]
57        #[doc = r" assert_eq!(v.as_zero_based(), 4);"]
58        #[doc = r""]
59        #[doc = r" // Creates from 0-based index"]
60        #[doc = concat!(r" let v = ", stringify!($name),r"::from_zero_based(0).unwrap();")]
61        #[doc = r" assert_eq!(v.as_one_based().get(), 1);"]
62        /// ```
63        #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
64        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
65        pub struct $name($nonzerotype);
66
67        impl OneBased for $name {
68            type IntType = $itype;
69            type NonZeroType = $nonzerotype;
70        }
71
72        impl Display for $name {
73            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74                self.as_one_based().fmt(f)
75            }
76        }
77
78        impl FromStr for $name {
79            type Err = ParseIntError;
80
81            fn from_str(s: &str) -> Result<Self, Self::Err> {
82                let v: $nonzerotype = s.parse()?;
83                Ok(Self::from_one_based_nonzero(v))
84            }
85        }
86
87        impl $name {
88            /// Creates `$name` from 1-based index value.
89            /// Returns `None` if the given index is zero.
90            ///
91            /// Note you can define a constant given [`Option::unwrap`] is also `const`.
92            /// ```
93            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
94           #[doc = concat!(r" const ONE_BASED_TEN: ", stringify!($name), r" = ", stringify!($name), r#"::from_one_based(10).expect("10 is non zero");"#)]
95            /// assert_eq!(ONE_BASED_TEN.as_zero_based(), 9);
96            /// ```
97            #[inline]
98            pub const fn from_one_based(v: $itype) -> Option<Self> {
99                match <$nonzerotype>::new(v) {
100                    None => None,
101                    Some(v) => Some($name(v)),
102                }
103            }
104
105            /// Creates `$name` from 1-based index value without check.
106            ///
107            /// # Safety
108            ///
109            /// Input must be greater than zero.
110            #[inline]
111            pub const unsafe fn from_one_based_unchecked(v: $itype) -> Self {
112                $name(<$nonzerotype>::new_unchecked(v))
113            }
114
115            /// Creates `$name` from 1-based index value as [`$nonzerotype`].
116            /// This will always succeed.
117            #[inline]
118            pub const fn from_one_based_nonzero(v: $nonzerotype) -> Self {
119                Self(v)
120            }
121
122            /// Creates `$name` from 0-based index value.
123            /// Returns `None` if the given index is MAX value,
124            /// as that would cause overflow when converted to 1-based.
125            #[inline]
126            pub const fn from_zero_based(v: $itype) -> Option<Self> {
127                if v == <$nonzerotype>::MAX.get() {
128                    return None;
129                }
130                // this won't overflow, and cannot be zero (note all $itype is unsigned).
131                Some($name(unsafe { <$nonzerotype>::new_unchecked(v + 1) }))
132            }
133
134            /// Creates `$name` from 0-based index value without check.
135            ///
136            /// # Safety
137            #[doc = concat!(r" This function results in undefined behavior when `v == ", stringify!($itype), r"::MAX`.")]
138            /// ```no_run
139            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
140            /// // This should cause undefined behavior
141            /// unsafe {
142            #[doc = concat!(r"     ", stringify!($name), "::from_zero_based_unchecked(", stringify!($itype), r"::MAX);")]
143            /// }
144            /// ```
145            #[inline]
146            pub const unsafe fn from_zero_based_unchecked(v: $itype) -> Self {
147                // this won't overflow, and cannot be zero (note all $itype is unsigned).
148                $name(unsafe { <$nonzerotype>::new_unchecked(v + 1) })
149            }
150
151            /// Returns regular 0-based index.
152            pub const fn as_zero_based(&self) -> $itype {
153                self.0.get() - 1
154            }
155
156            /// Returns 1-based index.
157            pub const fn as_one_based(&self) -> $nonzerotype {
158                self.0
159            }
160        }
161    };
162}
163
164define_one_based!(OneBasedU8, u8, NonZeroU8);
165define_one_based!(OneBasedU16, u16, NonZeroU16);
166define_one_based!(OneBasedU32, u32, NonZeroU32);
167define_one_based!(OneBasedU64, u64, NonZeroU64);
168define_one_based!(OneBasedU128, u128, NonZeroU128);
169define_one_based!(OneBasedUsize, usize, NonZeroUsize);
170
171macro_rules! impl_from_one_based {
172    ($source:ty => $($target:ty),+) => {$(
173        impl core::convert::From<$source> for $target {
174            #[doc = concat!(r"Converts [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
175            #[inline]
176            fn from(value: $source) -> Self {
177                use core::convert::Into as _;
178                let v: <$target as OneBased>::NonZeroType = value.as_one_based().into();
179                <$target>::from_one_based_nonzero(v)
180            }
181        }
182    )*};
183}
184
185impl_from_one_based!(OneBasedU8  => OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);
186impl_from_one_based!(OneBasedU16 => OneBasedU32, OneBasedU64, OneBasedU128);
187impl_from_one_based!(OneBasedU32 => OneBasedU64, OneBasedU128);
188impl_from_one_based!(OneBasedU64 => OneBasedU128);
189
190macro_rules! impl_try_from_one_based {
191    ($source:ty => $($target:ty),+) => {$(
192        impl core::convert::TryFrom<$source> for $target {
193            type Error = core::num::TryFromIntError;
194
195            #[doc = concat!(r"Attempts to convert [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
196            #[inline]
197            fn try_from(value: $source) -> Result<Self, Self::Error> {
198                use core::convert::TryInto as _;
199                let v: <$target as OneBased>::NonZeroType = value.as_one_based().try_into()?;
200                Ok(<$target>::from_one_based_nonzero(v))
201            }
202        }
203    )*};
204}
205
206impl_try_from_one_based!(OneBasedU8 => OneBasedUsize);
207impl_try_from_one_based!(OneBasedU16 => OneBasedUsize, OneBasedU8);
208impl_try_from_one_based!(OneBasedU32 => OneBasedUsize, OneBasedU8, OneBasedU16);
209impl_try_from_one_based!(OneBasedU64 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32);
210impl_try_from_one_based!(OneBasedU128 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64);
211impl_try_from_one_based!(OneBasedUsize => OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);