Skip to main content

devela/num/grain/niche/
absence.rs

1// devela/src/num/grain/niche/absence.rs
2//
3//! Absence of niche constraints and commitments.
4//!
5//! This module defines building blocks for working *around* niche-optimized
6//! numeric representations, either by explicitly opting out of layout
7//! optimization or by abstracting over whether such optimization is used.
8//!
9//! - [`MaybeNiche`] represents the **absence of representation commitment**:
10//!   it abstracts over primitive integers, niche-optimized types, and their
11//!   non-optimized counterparts, allowing generic code to remain independent
12//!   of the chosen representation.
13//!
14//! - [`NonNiche`] represents the **absence of niche constraints**: it mirrors
15//!   the API of niche-constrained numeric types while storing values unchanged.
16//!
17//! These types are complementary: one selects a concrete, non-optimized
18//! representation, while the other erases the distinction between optimized
19//! and non-optimized forms.
20//
21// TOC
22// - struct MaybeNiche
23// - struct NonNiche
24// - mod _test
25
26use crate::{
27    Cast, ConstInit, InvalidValue, NicheValueError, NonValueI8, NonValueI16, NonValueI32,
28    NonValueI64, NonValueI128, NonValueIsize, NonValueU8, NonValueU16, NonValueU32, NonValueU64,
29    NonValueU128, NonValueUsize, NonZero, Overflow, unwrap,
30};
31
32#[doc = crate::_tags!(maybe niche)]
33/// A zero-cost wrapper that abstracts over niche and non-niche types.
34#[doc = crate::_doc_meta!{
35    location("num/grain/niche", struct MaybeNiche),
36}]
37/// `MaybeNiche<T>` is a transparent wrapper that preserves the representation
38/// semantics of `T` without imposing a niche choice, and introduces no
39/// invariants of its own.
40///
41/// It enables niche-agnostic generic code over integer-like representations.
42///
43/// See also [`NonNiche`], which provides an explicit non-optimized representation
44/// with the same public API as niche-constrained types.
45///
46/// # Implementations
47///
48/// Implemented for:
49/// - [Primitive integers](#impl-MaybeNiche<u8>):
50///   `u8`, `u16`, `u32`, `u64`, `u128`, `usize`,
51///   `i8`, `i16`, `i32`, `i64`, `i128`, `isize`.
52/// - [`NonNiche<T>`](#impl-MaybeNiche<NonNiche<u8>>) for all supported primitives.
53/// - [`NonZero<T>`](#impl-MaybeNiche<NonZero<u8>>) for all supported primitives.
54/// - [`NonValue*<V>`](#impl-MaybeNiche<NonValueU8<V>>) for all supported primitives.
55///
56/// # Methods and constants
57///
58/// The API is identical across all implementations
59/// (links below point to the `u8` specialization).
60///
61/// - Niche properties: [`IS_NICHE`](#associatedconstant.IS_NICHE),
62///   [`is_niche`](#method.is_niche).
63/// - Contiguity: [`IS_CONTIGUOUS`](#associatedconstant.IS_CONTIGUOUS),
64///   [`is_contiguous`](#method.is_contiguous).
65/// - Signedness: [`HAS_NEGATIVE`](#associatedconstant.HAS_NEGATIVE),
66///   [`has_negative`](#method.has_negative).
67/// - Bounds: [`MIN`](#associatedconstant.MIN), [`MAX`](#associatedconstant.MAX).
68/// - Zero: [`ZERO`](#associatedconstant.ZERO), [`has_zero`](#method.has_zero).
69/// - Construction:
70///   - From `T`:
71///     [`new`](#method.new) / `MaybeNiche(T)`.
72///   - From primitive:
73///     [`try_from_prim`](#method.try_from_prim),
74///     [`from_prim_lossy`](#method.from_prim_lossy).
75///   - From `usize`:
76///     [`try_from_usize`](#method.try_from_usize),
77///     [`from_usize_saturating`](#method.from_usize_saturating),
78///     [`from_usize_wrapping`](#method.from_usize_wrapping).
79/// - Extraction:
80///   - To `T`:
81///     [`get`](#method.get)/[`repr`](#method.repr).
82///   - To primitive:
83///     [`get_prim`](#method.get_prim)/[`prim`](#method.prim).
84///   - To `usize`:
85///     [`try_to_usize`](#method.try_to_usize),
86///     [`to_usize_saturating`](#method.to_usize_saturating),
87///     [`to_usize_wrapping`](#method.to_usize_wrapping).
88#[repr(transparent)]
89#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
90pub struct MaybeNiche<T: Copy>(pub T);
91
92/// impl helper for [`MaybeNiche`].
93macro_rules! impl_maybe {
94    () => {
95        impl_maybe!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
96    };
97    ($($T:ty),+) => {
98        // % $is_niche, $prim, $T
99        //   $(,<const V: $V>) $(,*$get)? $(,^$new)? $(,@$non0)?
100        // ------------------------------------------------------
101        $( impl_maybe![% false, $T, $T]; )+
102        $( impl_maybe![% false, $T, NonNiche<$T>, *get, ^new]; )+
103        $( impl_maybe![% true,  $T, NonZero<$T>, *get, ^new, @non0]; )+
104        $crate::paste!{ $(
105            impl_maybe![% true, $T, [<NonValue $T:camel>] <V>, <const V: $T>,
106            *get, ^new];
107        )+ }
108    };
109    (%
110     $is_niche:literal, // IS_NICHE
111     $prim:ty,
112     $T:ty $(, <const $V:ident : $v:ty>)?
113     $(, *$get:ident)?  // for: get_prim, prim
114     $(, ^$new:ident)?  // for: from_prim, *_unchecked
115     $(, @$non0:ident)? // identifies nonzero types, for: from_prim_lossy
116    ) => {
117        impl $(<const $V: $v>)? ConstInit for MaybeNiche<$T> where $T: ConstInit {
118            const INIT: Self = Self::new(<$T>::INIT);
119        }
120
121        impl $(<const $V: $v>)? MaybeNiche<$T> {
122            /* constants */
123
124            /// Whether this representation uses a memory niche for layout optimization.
125            pub const IS_NICHE: bool = $is_niche;
126
127            /// Whether the representable domain forms a contiguous interval.
128            ///
129            /// That is, whether every primitive value `v` such that
130            /// `MIN <= v <= MAX` is representable.
131            pub const IS_CONTIGUOUS: bool = {
132                // primitives, NonNiche, NonZero
133                #[crate::compile(none($(<const $V: $v>)?))]
134                const fn _is_contiguous $(<const $V: $v>)? () -> bool { true }
135                // NonValue
136                #[crate::compile(some($(<const $V: $v>)?))]
137                const fn _is_contiguous $(<const $V: $v>)? () -> bool {
138                    V == <$prim>::MIN || V == <$prim>::MAX
139                }
140                _is_contiguous$(::<$V>)?()
141            };
142
143            /// Whether the representable domain includes negative values.
144            #[allow(unused_comparisons, reason = "for unsigned types")]
145            pub const HAS_NEGATIVE: bool = Self::MIN.prim() < 0;
146
147            /// The minimum representable value.
148            pub const MIN: Self = Self(<$T>::MIN);
149            /// The maximum representable value.
150            pub const MAX: Self = Self(<$T>::MAX);
151
152            /// The zero value, if representable by this type.
153            pub const ZERO: Option<Self> = unwrap![ok_some Self::try_from_prim(0)];
154
155            /* constructors */
156
157            /// Creates a new `MaybeNiche` containing `value`.
158            #[must_use]
159            pub const fn new(value: $T) -> Self { Self(value) }
160
161            /// Creates a new `MaybeNiche` from a primitive value.
162            /// # Errors
163            /// - [`InvalidValue`] if the value violates the validity invariant of `T`.
164            pub const fn try_from_prim(primitive: $prim) -> Result<Self, InvalidValue> {
165                // WAIT: [proc_macro_hygiene](https://github.com/rust-lang/rust/issues/54727)
166                // Can't use `compile` on expr or stmt, e.g.: return Some(Self(primitive));
167                // so we need to leverage defined functions instead.
168
169                // NonNiche, NonValue, NonZero
170                #[crate::compile(some($($new)?))]
171                const fn _new $(<const $V: $v>)? (v: $prim) -> Option<$T> { $( <$T>::$new(v) )? }
172                // primitives
173                #[crate::compile(none($($new)?))]
174                const fn _new $(<const $V: $v>)? (v: $prim) -> Option<$T> { Some(v) }
175
176                Ok(Self(unwrap![some_ok_or? _new(primitive), crate::InvalidValue]))
177            }
178            /// Creates a new `MaybeNiche` without any checks.
179            /// # Safety
180            /// For niche-optimized types, callers must ensure that
181            /// `value` satisfies the variant's validity constraints.
182            #[must_use]
183            #[cfg(all(not(feature = "safe_num"), feature = "unsafe_niche"))]
184            #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_niche")))]
185            pub const unsafe fn from_prim_unchecked(primitive: $prim) -> Self {
186                // NonNiche, NonValue, NonZero
187                #[crate::compile(some($($new)?))]
188                const fn _new $(<const $V: $v>)? (v: $prim) -> $T { $crate::paste! {
189                    unsafe { $( <$T>:: [< $new _unchecked >](v) )? }
190                }}
191                // primitives
192                #[crate::compile(none($($new)?))]
193                const fn _new $(<const $V: $v>)? (v: $prim) -> $T { v }
194
195                Self(_new(primitive))
196            }
197
198            /// Creates a new `MaybeNiche` from a primitive value, converting invalid inputs
199            /// into a valid but *approximate* representation.
200            ///
201            /// This constructor performs a **lossy conversion**, applying a best-effort
202            /// fallback when the primitive violates the underlying type's invariant:
203            ///
204            /// - For `NonZero*` types, `0` becomes the smallest valid value (`MIN`).
205            /// - For `NonValue*` types, conversion defers to their own
206            ///   [`new_lossy`](NonValueU8::new_lossy)-style semantics.
207            /// - For `NonNiche` and primitive integers, the value is used as-is.
208            #[must_use]
209            pub const fn from_prim_lossy(value: $prim) -> Self {
210                // NonZero converts
211                #[crate::compile(all(some($($new)?), some($($non0)?)))]
212                const fn _lossy $(<const $V: $v>)? (v: $prim) -> $T {
213                    if v == 0 { <$T>::MIN }
214                    else {
215                        cfg_select! { all(feature = "unsafe_niche", not(feature = "safe_num")) => {
216                            unwrap![some_guaranteed_or_ub <$T>::new(v)]
217                        } _ => { unwrap![some <$T>::new(v)] }}
218                    }
219                }
220                // NonNiche, NonValue (has its own rules)
221                #[crate::compile(all(some($($new)?), none($($non0)?)))]
222                const fn _lossy $(<const $V: $v>)? (v: $prim) -> $T { $crate::paste! {
223                    $( <$T>:: [< $new _lossy >](v) )?
224                }}
225                // primitives
226                #[crate::compile(none($($new)?))]
227                const fn _lossy $(<const $V: $v>)? (v: $prim) -> $T { v }
228
229                Self(_lossy(value))
230            }
231
232            /// Tries to create a new `MaybeNiche` from a `usize`.
233            /// # Errors
234            /// - [`NicheValueError::Overflow`] if the value cannot be represented by the
235            ///   underlying primitive type.
236            /// - [`NicheValueError::InvalidValue`] if the value violates the validity
237            ///   invariant of `T`.
238            pub const fn try_from_usize(value: usize) -> Result<Self, NicheValueError> {
239                // NonNiche, NonValue, NonZero
240                #[crate::compile(some($($new)?))]
241                const fn _new $(<const $V: $v>)? (v: $prim) -> Option<$T> { $( <$T>::$new(v) )? }
242                // primitives
243                #[crate::compile(none($($new)?))]
244                const fn _new $(<const $V: $v>)? (v: $prim) -> Option<$T> { Some(v) }
245
246                let prim = $crate::paste! { Cast(value).[<checked_cast_to_ $prim>]() };
247                let prim = unwrap![ok_err_map? prim, |e| NicheValueError::from_overflow(e)];
248                Ok(Self(unwrap![some_ok_or? _new(prim), NicheValueError::InvalidValue]))
249            }
250
251            /// Creates a new `MaybeNiche` from a `usize`, saturating at numeric bounds.
252            ///
253            /// The conversion applies the following steps:
254            /// 1. The `usize` value is saturated to the bounds of the primitive type.
255            /// 2. If the resulting value violates the niche invariant of `T`,
256            ///    a best-effort lossy conversion is applied.
257            #[must_use]
258            pub const fn from_usize_saturating(value: usize) -> Self {
259                let prim = $crate::paste! { Cast(value).[<saturating_cast_to_ $prim>]() };
260                Self::from_prim_lossy(prim)
261            }
262
263            /// Creates a new `MaybeNiche` from a `usize`, wrapping at numeric bounds.
264            ///
265            /// The conversion applies the following steps:
266            /// 1. The `usize` value is wrapped to the primitive type.
267            /// 2. If the resulting value violates the niche invariant of `T`,
268            ///    a best-effort lossy conversion is applied.
269            #[must_use]
270            pub const fn from_usize_wrapping(value: usize) -> Self {
271                let prim = $crate::paste! { Cast(value).[<wrapping_cast_to_ $prim>]() };
272                Self::from_prim_lossy(prim)
273            }
274
275            /* queries */
276
277            /// Returns `true` if this representation uses a memory niche.
278            #[must_use]
279            pub const fn is_niche(self) -> bool { Self::IS_NICHE }
280
281            /// Returns `true` if the representable domain is contiguous.
282            ///
283            /// That is, if every primitive value `v` such that `MIN <= v <= MAX` is representable.
284            #[must_use]
285            pub const fn is_contiguous(self) -> bool { Self::IS_CONTIGUOUS }
286
287            /// Returns `true` if the representable domain includes negative values.
288            #[must_use]
289            pub const fn has_negative(self) -> bool { Self::HAS_NEGATIVE }
290
291            /// Returns `true` if this type can represent zero.
292            #[must_use]
293            pub const fn has_zero(self) -> bool { Self::ZERO.is_some() }
294
295            /// Returns `true` if `self == other`.
296            #[must_use]
297            pub const fn eq(self, other: Self) -> bool { self.prim() == other.prim() }
298            /// Returns `true` if `self != other`.
299            #[must_use]
300            pub const fn ne(self, other: Self) -> bool { self.prim() != other.prim() }
301            /// Returns `true` if `self < other`.
302            #[must_use]
303            pub const fn lt(self, other: Self) -> bool { self.prim() < other.prim() }
304            /// Returns `true` if `self <= other`.
305            #[must_use]
306            pub const fn le(self, other: Self) -> bool { self.prim() <= other.prim() }
307            /// Returns `true` if `self > other`.
308            #[must_use]
309            pub const fn gt(self, other: Self) -> bool { self.prim() > other.prim() }
310            /// Returns `true` if `self >= other`.
311            #[must_use]
312            pub const fn ge(self, other: Self) -> bool { self.prim() >= other.prim() }
313
314            /* representation access */
315
316            /// Returns the validated (niche-aware) representation.
317            #[must_use]
318            pub const fn get(self) -> $T { self.0 }
319
320            /// Alias of [`get`][Self::get], emphasizing representational access.
321            #[must_use]
322            pub const fn repr(self) -> $T { self.get() }
323
324            /* primitive access */
325
326            /// Returns the primitive carrier value.
327            #[must_use]
328            pub const fn get_prim(self) -> $prim { self.0 $( . $get() )? }
329
330            /// Alias of [`get_prim`][Self::get_prim], emphasizing primitive access.
331            #[must_use]
332            pub const fn prim(self) -> $prim { self.get_prim() }
333
334            /* casts */
335
336            /// Converts the value into a `usize`, returning an error on overflow.
337            ///
338            /// # Errors
339            /// Will return [`Overflow`] if `self` can't fit in a `usize`.
340            pub const fn try_to_usize(self) -> Result<usize, Overflow> {
341                Cast(self.get_prim()).checked_cast_to_usize()
342            }
343            /// Converts the value into a `usize`, saturating at the numeric bounds.
344            #[must_use]
345            pub const fn to_usize_saturating(self) -> usize {
346                Cast(self.get_prim()).saturating_cast_to_usize()
347            }
348            /// Converts the value into a `usize`, wrapping at the numeric bounds.
349            #[must_use]
350            pub const fn to_usize_wrapping(self) -> usize {
351                Cast(self.get_prim()).wrapping_cast_to_usize()
352            }
353        }
354    };
355}
356impl_maybe![];
357
358#[doc = crate::_tags!(no niche)]
359/// A zero-cost wrapper that mimics a niche type without using a niche.
360#[doc = crate::_doc_meta!{
361    location("num/grain/niche", struct NonNiche),
362}]
363/// `NonNiche` represents the absence of niche constraints while preserving
364/// API symmetry with niche-optimized numeric types.
365///
366/// Practical note:
367///
368/// `NonNiche<T>` is a concrete representation choice. It gives you a parallel,
369/// non-optimized version of a type (e.g. fast vs compact) while keeping the
370/// same public API and implementation surface.
371///
372/// Used in types like `charu` to provide a non-optimized parallel to
373/// their niche-enabled counterparts.
374///
375/// See also [`MaybeNiche`], which abstracts over primitive, niche-optimized,
376/// and non-optimized integer representations.
377#[repr(transparent)]
378#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
379pub struct NonNiche<T: Copy>(pub T);
380
381#[rustfmt::skip]
382impl<T: Copy> NonNiche<T> {
383    /// Creates a new `NonNiche` with the given value.
384    ///
385    /// This always succeeds, unlike `NonZero*` types.
386    #[must_use]
387    pub const fn new(value: T) -> Option<Self> { Some(Self(value)) }
388
389    /// Creates a new `NonNiche` without checking.
390    /// # Safety
391    /// This is always safe since `NonNiche` doesn't have any validity constraints.
392    /// Method provided for API completion.
393    #[must_use]
394    #[cfg(all(not(feature = "safe_num"), feature = "unsafe_niche"))]
395    #[cfg_attr(nightly_doc, doc(cfg(feature = "unsafe_niche")))]
396    pub const unsafe fn new_unchecked(value: T) -> Self { Self(value) }
397
398    /// Creates a NonNiche, automatically converting any prohibited values.
399    ///
400    /// There are no prohibited values. Method provided for API completion.
401    #[must_use]
402    pub const fn new_lossy(value: T) -> Self { Self(value) }
403
404    /// Extracts the inner value.
405    #[must_use]
406    pub const fn get(self) -> T { self.0 }
407}
408
409#[rustfmt::skip]
410impl<T: Copy> From<T> for NonNiche<T> {
411    fn from(value: T) -> Self { Self(value) }
412}
413
414impl<T: Copy + ConstInit> ConstInit for NonNiche<T> {
415    const INIT: Self = Self(T::INIT);
416}
417
418// helper make implementations over primitives.
419macro_rules! impl_non {
420    () => { impl_non!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize); };
421    ($($prim:ty),+) => {
422        $(
423            impl NonNiche<$prim> {
424                /// The minimum possible value.
425                pub const MIN: Self = Self(<$prim>::MIN);
426                /// The maximum possible value.
427                pub const MAX: Self = Self(<$prim>::MAX);
428            }
429        )+
430    };
431}
432impl_non![];
433
434#[cfg(test)]
435mod _test {
436    use super::{MaybeNiche, NonNiche, NonValueU8, NonZero};
437
438    #[test]
439    fn maybe_niche() {
440        let u = MaybeNiche(3_u8);
441        let nn = MaybeNiche(NonNiche::<u8>::new(3).unwrap());
442        let n0 = MaybeNiche(NonZero::<u8>::new(3).unwrap());
443        let nv0 = MaybeNiche(NonValueU8::<0>::new(3).unwrap());
444        let nv1 = MaybeNiche(NonValueU8::<1>::new(3).unwrap());
445
446        // u8
447        assert_eq![u.is_contiguous(), true];
448        assert_eq![u.is_niche(), false];
449        assert_eq![u.has_zero(), true];
450        // NonNiche
451        assert_eq![nn.is_contiguous(), true];
452        assert_eq![nn.is_niche(), false];
453        assert_eq![nn.has_zero(), true];
454        // NonZero
455        assert_eq![n0.is_contiguous(), true];
456        assert_eq![n0.is_niche(), true];
457        assert_eq![n0.has_zero(), false];
458        // NonValue::<0>
459        assert_eq![nv0.is_contiguous(), true];
460        assert_eq![nv0.is_niche(), true];
461        assert_eq![nv0.has_zero(), false];
462        // NonValue::<1>
463        assert_eq![nv1.is_contiguous(), false];
464        assert_eq![nv1.is_niche(), true];
465        assert_eq![nv1.has_zero(), true];
466    }
467}