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        #[doc = concat!(r" `", stringify!($name), r"` 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            /// The size of this one-based integer type in bits.
89            ///
90            #[doc = concat!(r" This value is equal to ", stringify!($itype), r"::BITS.")]
91            pub const BITS: u32 = <$itype>::BITS;
92
93            /// The smallest value that can be represented by this one-based integer type, `from_one_based(1)`.
94            pub const MIN: Self = Self::from_one_based_nonzero(<$nonzerotype>::MIN);
95
96            #[doc = concat!(r" The largest value that can be represented by this one-based integer type, equal to `from_one_based(", stringify!($itype), r"::MAX)`.")]
97            pub const MAX: Self = Self::from_one_based_nonzero(<$nonzerotype>::MAX);
98
99            /// Creates a one-based integer from 1-based index value.
100            /// Returns `None` if the given index is zero.
101            ///
102            /// Note you can define a constant easily given [`Option::unwrap()`] or [`Option::expect()`] are also `const`.
103            /// ```
104            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
105           #[doc = concat!(r" const ONE_BASED_TEN: ", stringify!($name), r" = ", stringify!($name), r#"::from_one_based(10).expect("10 is non zero");"#)]
106            /// assert_eq!(ONE_BASED_TEN.as_zero_based(), 9);
107            /// ```
108            #[inline]
109            pub const fn from_one_based(v: $itype) -> Option<Self> {
110                match <$nonzerotype>::new(v) {
111                    None => None,
112                    Some(v) => Some($name(v)),
113                }
114            }
115
116            /// Creates a one-based integer from 1-based index value without check.
117            ///
118            /// # Safety
119            ///
120            /// Input must be greater than zero.
121            #[inline]
122            pub const unsafe fn from_one_based_unchecked(v: $itype) -> Self {
123                $name(<$nonzerotype>::new_unchecked(v))
124            }
125
126            /// Creates a one-based integer from non-zero 1-based index value.
127            ///
128            /// Given the underlying value is guaranteed to be non-zero, this will always succeed.
129            #[inline]
130            pub const fn from_one_based_nonzero(v: $nonzerotype) -> Self {
131                Self(v)
132            }
133
134            /// Creates a one-based integer from 0-based index value.
135            /// Returns `None` if the given index is MAX value,
136            /// as that would cause overflow when converted to 1-based.
137            #[inline]
138            pub const fn from_zero_based(v: $itype) -> Option<Self> {
139                if v == <$nonzerotype>::MAX.get() {
140                    return None;
141                }
142                // this won't overflow, and cannot be zero (note all $itype is unsigned).
143                Some($name(unsafe { <$nonzerotype>::new_unchecked(v + 1) }))
144            }
145
146            /// Creates a one-based integer from 0-based index value without check.
147            ///
148            /// # Safety
149            #[doc = concat!(r" This function results in undefined behavior when `v == ", stringify!($itype), r"::MAX`.")]
150            /// ```no_run
151            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
152            /// // This should cause undefined behavior
153            /// unsafe {
154            #[doc = concat!(r"     ", stringify!($name), "::from_zero_based_unchecked(", stringify!($itype), r"::MAX);")]
155            /// }
156            /// ```
157            #[inline]
158            pub const unsafe fn from_zero_based_unchecked(v: $itype) -> Self {
159                // this won't overflow, and cannot be zero (note all $itype is unsigned).
160                $name(unsafe { <$nonzerotype>::new_unchecked(v + 1) })
161            }
162
163            /// Returns regular 0-based index.
164            pub const fn as_zero_based(self) -> $itype {
165                self.0.get() - 1
166            }
167
168            /// Returns 1-based index.
169            pub const fn as_one_based(self) -> $nonzerotype {
170                self.0
171            }
172
173            /// Adds an unsigned integer to a one-based integer value. Checks for overflow and returns `None` on overflow.
174            ///
175            #[doc = r" ```"]
176            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
177            #[doc = concat!(r" let one = ", stringify!($name), "::from_zero_based(1).unwrap();")]
178            #[doc = concat!(r" let two = ", stringify!($name), "::from_zero_based(2).unwrap();")]
179            #[doc = concat!(r" let max = ", stringify!($name), "::MAX;")]
180            #[doc = r""]
181            #[doc = r" assert_eq!(Some(two), one.checked_add(1));"]
182            #[doc = r" assert_eq!(None, max.checked_add(1));"]
183            #[doc = r" ```"]
184            pub const fn checked_add(self, other: $itype) -> Option<Self> {
185                match self.0.checked_add(other) {
186                    None => None,
187                    Some(v) => Some(Self(v)),
188                }
189            }
190
191            /// Adds an unsigned integer to a one-based integer value. Returns [`Self::MAX`] on overflow.
192            ///
193            #[doc = r" ```"]
194            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
195            #[doc = concat!(r" let one = ", stringify!($name), "::from_zero_based(1).unwrap();")]
196            #[doc = concat!(r" let two = ", stringify!($name), "::from_zero_based(2).unwrap();")]
197            #[doc = concat!(r" let max = ", stringify!($name), "::MAX;")]
198            #[doc = r""]
199            #[doc = r" assert_eq!(two, one.saturating_add(1));"]
200            #[doc = r" assert_eq!(max, max.saturating_add(1));"]
201            #[doc = r" ```"]
202            pub const fn saturating_add(self, other: $itype) -> Self {
203                Self(self.0.saturating_add(other))
204            }
205        }
206    };
207}
208
209define_one_based!(OneBasedU8, u8, NonZeroU8);
210define_one_based!(OneBasedU16, u16, NonZeroU16);
211define_one_based!(OneBasedU32, u32, NonZeroU32);
212define_one_based!(OneBasedU64, u64, NonZeroU64);
213define_one_based!(OneBasedU128, u128, NonZeroU128);
214define_one_based!(OneBasedUsize, usize, NonZeroUsize);
215
216macro_rules! impl_from_one_based {
217    ($source:ty => $($target:ty),+) => {$(
218        impl core::convert::From<$source> for $target {
219            #[doc = concat!(r"Converts [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
220            #[inline]
221            fn from(value: $source) -> Self {
222                use core::convert::Into as _;
223                let v: <$target as OneBased>::NonZeroType = value.as_one_based().into();
224                <$target>::from_one_based_nonzero(v)
225            }
226        }
227    )*};
228}
229
230impl_from_one_based!(OneBasedU8  => OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);
231impl_from_one_based!(OneBasedU16 => OneBasedU32, OneBasedU64, OneBasedU128);
232impl_from_one_based!(OneBasedU32 => OneBasedU64, OneBasedU128);
233impl_from_one_based!(OneBasedU64 => OneBasedU128);
234
235macro_rules! impl_try_from_one_based {
236    ($source:ty => $($target:ty),+) => {$(
237        impl core::convert::TryFrom<$source> for $target {
238            type Error = core::num::TryFromIntError;
239
240            #[doc = concat!(r"Attempts to convert [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
241            #[inline]
242            fn try_from(value: $source) -> Result<Self, Self::Error> {
243                use core::convert::TryInto as _;
244                let v: <$target as OneBased>::NonZeroType = value.as_one_based().try_into()?;
245                Ok(<$target>::from_one_based_nonzero(v))
246            }
247        }
248    )*};
249}
250
251impl_try_from_one_based!(OneBasedU8 => OneBasedUsize);
252impl_try_from_one_based!(OneBasedU16 => OneBasedUsize, OneBasedU8);
253impl_try_from_one_based!(OneBasedU32 => OneBasedUsize, OneBasedU8, OneBasedU16);
254impl_try_from_one_based!(OneBasedU64 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32);
255impl_try_from_one_based!(OneBasedU128 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64);
256impl_try_from_one_based!(OneBasedUsize => OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);