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            /// The size of this non-zero 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 non-zero 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 non-zero 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 `$name` from 1-based index value.
100            /// Returns `None` if the given index is zero.
101            ///
102            /// Note you can define a constant given [`Option::unwrap`] is 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 `$name` 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 `$name` from 1-based index value as [`$nonzerotype`].
127            /// This will always succeed.
128            #[inline]
129            pub const fn from_one_based_nonzero(v: $nonzerotype) -> Self {
130                Self(v)
131            }
132
133            /// Creates `$name` from 0-based index value.
134            /// Returns `None` if the given index is MAX value,
135            /// as that would cause overflow when converted to 1-based.
136            #[inline]
137            pub const fn from_zero_based(v: $itype) -> Option<Self> {
138                if v == <$nonzerotype>::MAX.get() {
139                    return None;
140                }
141                // this won't overflow, and cannot be zero (note all $itype is unsigned).
142                Some($name(unsafe { <$nonzerotype>::new_unchecked(v + 1) }))
143            }
144
145            /// Creates `$name` from 0-based index value without check.
146            ///
147            /// # Safety
148            #[doc = concat!(r" This function results in undefined behavior when `v == ", stringify!($itype), r"::MAX`.")]
149            /// ```no_run
150            #[doc = concat!(r" # use one_based::", stringify!($name), r";")]
151            /// // This should cause undefined behavior
152            /// unsafe {
153            #[doc = concat!(r"     ", stringify!($name), "::from_zero_based_unchecked(", stringify!($itype), r"::MAX);")]
154            /// }
155            /// ```
156            #[inline]
157            pub const unsafe fn from_zero_based_unchecked(v: $itype) -> Self {
158                // this won't overflow, and cannot be zero (note all $itype is unsigned).
159                $name(unsafe { <$nonzerotype>::new_unchecked(v + 1) })
160            }
161
162            /// Returns regular 0-based index.
163            pub const fn as_zero_based(&self) -> $itype {
164                self.0.get() - 1
165            }
166
167            /// Returns 1-based index.
168            pub const fn as_one_based(&self) -> $nonzerotype {
169                self.0
170            }
171        }
172    };
173}
174
175define_one_based!(OneBasedU8, u8, NonZeroU8);
176define_one_based!(OneBasedU16, u16, NonZeroU16);
177define_one_based!(OneBasedU32, u32, NonZeroU32);
178define_one_based!(OneBasedU64, u64, NonZeroU64);
179define_one_based!(OneBasedU128, u128, NonZeroU128);
180define_one_based!(OneBasedUsize, usize, NonZeroUsize);
181
182macro_rules! impl_from_one_based {
183    ($source:ty => $($target:ty),+) => {$(
184        impl core::convert::From<$source> for $target {
185            #[doc = concat!(r"Converts [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
186            #[inline]
187            fn from(value: $source) -> Self {
188                use core::convert::Into as _;
189                let v: <$target as OneBased>::NonZeroType = value.as_one_based().into();
190                <$target>::from_one_based_nonzero(v)
191            }
192        }
193    )*};
194}
195
196impl_from_one_based!(OneBasedU8  => OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);
197impl_from_one_based!(OneBasedU16 => OneBasedU32, OneBasedU64, OneBasedU128);
198impl_from_one_based!(OneBasedU32 => OneBasedU64, OneBasedU128);
199impl_from_one_based!(OneBasedU64 => OneBasedU128);
200
201macro_rules! impl_try_from_one_based {
202    ($source:ty => $($target:ty),+) => {$(
203        impl core::convert::TryFrom<$source> for $target {
204            type Error = core::num::TryFromIntError;
205
206            #[doc = concat!(r"Attempts to convert [`", stringify!($source), r"`] to [`", stringify!($target), r"`].")]
207            #[inline]
208            fn try_from(value: $source) -> Result<Self, Self::Error> {
209                use core::convert::TryInto as _;
210                let v: <$target as OneBased>::NonZeroType = value.as_one_based().try_into()?;
211                Ok(<$target>::from_one_based_nonzero(v))
212            }
213        }
214    )*};
215}
216
217impl_try_from_one_based!(OneBasedU8 => OneBasedUsize);
218impl_try_from_one_based!(OneBasedU16 => OneBasedUsize, OneBasedU8);
219impl_try_from_one_based!(OneBasedU32 => OneBasedUsize, OneBasedU8, OneBasedU16);
220impl_try_from_one_based!(OneBasedU64 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32);
221impl_try_from_one_based!(OneBasedU128 => OneBasedUsize, OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64);
222impl_try_from_one_based!(OneBasedUsize => OneBasedU8, OneBasedU16, OneBasedU32, OneBasedU64, OneBasedU128);