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