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}