index_ext/
mem.rs

1//! Integer wrappers that are constrained to the range of `usize` or `isize`.
2//!
3//! This module defines a number of wrappers around underlying basic integer types which ensure
4//! that the value fits both within `usize` (respectively `isize`) as well as the basic integer
5//! type, without loss of data.
6//!
7//! Additionally, the types are optimized for size. That is, the types occupy the minimum size of
8//! both the underlying integer and the respective pointer sized alternative. This ensure that
9//! platform-compatible code is easier to write without wasting memory for the data representation
10//! in the process.
11//!
12//! # Usage
13//!
14//! Imagine some interface defining indices to be in the range of a `u64`. A 32-bit platform
15//! implementing this interface may be torn between using `u64` for the intent, as well as its own
16//! `isize` for the smaller representation. The corresponding type from this module can combine
17//! both properties. A demonstration by types beyond the size of most platforms:
18//!
19//! ```
20//! use core::mem::size_of;
21//! use index_ext::mem::Imem128;
22//!
23//! assert!(size_of::<Imem128>() <= size_of::<usize>());
24//! assert!(size_of::<Imem128>() <= size_of::<i128>());
25//! ```
26//!
27//! Keep in mind the types are most useful for representing values in and relative to your own
28//! program's memory. In the interface you would generally prefer to declare argument as the
29//! specified platform independent underlying integer (e.g. `u64` above). In these cases, there is
30//! however a security risk associated with delaying the (fallible) conversions to your platform's
31//! capabilities. Firstly, implicit loss of precision when using `as` may occur on some
32//! distributions but not others which may result in incomplete test coverage missing a bug.
33//! Secondly, utilizing fallible conversion at the site of use creates many error paths. You might
34//! prefer converting to `Umem64` immediately.
35//!
36//! Consider the case of a matrix where dimensions are stored as `u32` for compatibility reasons.
37//! We would now like to allocate a buffer for it which requires calculating the number of elements
38//! as a `u64` and then convert to `usize`. However, no matter in which number type you intend to
39//! store the result you lose semantic meaning because there is no 'proof' attached to the value
40//! that it will also fit into the other value range.
41//!
42//! ```
43//! use index_ext::mem::Umem64;
44//!
45//! struct Matrix {
46//!     width: u32,
47//!     height: u32,
48//! }
49//!
50//! # fn fake() -> Option<()> {
51//! # let mat = Matrix { width: 0, height: 0 };
52//! let elements = u64::from(mat.width) * u64::from(mat.height);
53//! let length: Umem64 = Umem64::new(elements)?;
54//!
55//! let matrix = vec![0; length.get()];
56//! # Some(()) }
57//! ```
58struct Choice<const C: bool>;
59
60trait Impl<T> {
61    type Inner;
62}
63
64macro_rules! lossless_integer {
65    (
66        $(#[$attr:meta])*
67        $sizet:ident $str_name:literal
68        pub struct $name:ident ($under:ty)
69    ) => {
70        impl Impl<$name> for Choice<true> {
71            type Inner = $under;
72        }
73        impl Impl<$name> for Choice<false> {
74            type Inner = $sizet;
75        }
76
77        $(#[$attr])*
78        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
79        #[repr(transparent)]
80        pub struct $name(
81            <
82                Choice<{core::mem::size_of::<$under>() < core::mem::size_of::<$sizet>()}>
83                as Impl<$name>
84            >::Inner
85        );
86
87        impl $name {
88            /// Wrap an integer if its value can be losslessly converted to `
89            #[doc = $str_name]
90            /// `.
91            pub fn new(val: $under) -> Option<Self> {
92                if <$sizet as core::convert::TryFrom<_>>::try_from(val).is_ok() {
93                    // Potentially no-op, potentially a cast. The macro and type deduction makes
94                    // sure either $under or $sizet is utilized as the result. Both are correct by
95                    // the input argument and the `try_from`.
96                    Some($name(val as _))
97                } else {
98                    None
99                }
100            }
101
102            /// Get the `
103            #[doc = $str_name]
104            /// ` value of the integer.
105            pub fn get(self) -> $sizet {
106                self.0 as $sizet
107            }
108
109            /// Get the inner value in the original type.
110            pub fn into_inner(self) -> $under {
111                self.0 as $under
112            }
113        }
114
115        impl From<$name> for $sizet {
116            fn from(val: $name) -> $sizet {
117                val.get()
118            }
119        }
120
121        impl From<$name> for $under {
122            fn from(val: $name) -> $under {
123                val.into_inner()
124            }
125        }
126
127        // Note: clippy says implementing `ne` is not necessary. We'll see about that if any
128        // performance complaints reach the repository.
129        impl PartialEq<$under> for $name {
130            fn eq(&self, other: &$under) -> bool {
131                self.into_inner() == *other
132            }
133        }
134        impl PartialEq<$name> for $under {
135            fn eq(&self, other: &$name) -> bool {
136                *self == other.into_inner()
137            }
138        }
139
140        impl PartialEq<$sizet> for $name {
141            fn eq(&self, other: &$sizet) -> bool {
142                self.get() == *other
143            }
144        }
145        impl PartialEq<$name> for $sizet {
146            fn eq(&self, other: &$name) -> bool {
147                *self == other.get()
148            }
149        }
150
151        impl PartialOrd<$under> for $name {
152            fn partial_cmp(&self, other: &$under) -> Option<core::cmp::Ordering> {
153                self.into_inner().partial_cmp(other)
154            }
155        }
156        impl PartialOrd<$name> for $under {
157            fn partial_cmp(&self, other: &$name) -> Option<core::cmp::Ordering> {
158                self.partial_cmp(&other.into_inner())
159            }
160        }
161
162        impl PartialOrd<$sizet> for $name {
163            fn partial_cmp(&self, other: &$sizet) -> Option<core::cmp::Ordering> {
164                self.get().partial_cmp(other)
165            }
166        }
167        impl PartialOrd<$name> for $sizet {
168            fn partial_cmp(&self, other: &$name) -> Option<core::cmp::Ordering> {
169                self.partial_cmp(&other.get())
170            }
171        }
172
173        impl core::fmt::Display for $name {
174            fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
175                // Uses the pointer type, avoiding any non-register-sized arguments to display and
176                // code gen for that. This one is more likely to be needed already.
177                self.get().fmt(f)
178            }
179        }
180    };
181}
182
183macro_rules! integer_mem {
184    ($(#[$attr:meta])* pub struct $name:ident ($under:ty)) => {
185        lossless_integer!($(#[$attr])*
186        usize "usize"
187        pub struct $name ($under));
188
189        impl<T> core::ops::Index<$name> for [T] {
190            type Output = T;
191            fn index(&self, idx: $name) -> &Self::Output {
192                &self[idx.get()]
193            }
194        }
195    }
196}
197
198macro_rules! integer_diff {
199    ($(#[$attr:meta])* pub struct $name:ident ($under:ty)) => {
200        lossless_integer!($(#[$attr])*
201        isize "isize"
202        pub struct $name ($under));
203    }
204}
205
206integer_mem!(
207    /// An i8 that is also in the value range of a usize.
208    pub struct Imem8(i8)
209);
210
211integer_mem!(
212    /// A u8 that is also in the value range of a usize.
213    pub struct Umem8(u8)
214);
215
216integer_mem!(
217    /// An i16 that is also in the value range of a usize.
218    pub struct Imem16(i16)
219);
220
221integer_mem!(
222    /// A u16 that is also in the value range of a usize.
223    pub struct Umem16(u16)
224);
225
226integer_mem!(
227    /// An i32 that is also in the value range of a usize.
228    pub struct Imem32(i32)
229);
230
231integer_mem!(
232    /// A u32 that is also in the value range of a usize.
233    pub struct Umem32(u32)
234);
235
236integer_mem!(
237    /// An i64 that is also in the value range of a usize.
238    pub struct Imem64(i64)
239);
240
241integer_mem!(
242    /// A u64 that is also in the value range of a usize.
243    pub struct Umem64(u64)
244);
245
246integer_mem!(
247    /// An i128 that is also in the value range of a usize.
248    pub struct Imem128(i128)
249);
250
251integer_mem!(
252    /// A u128 that is also in the value range of a usize.
253    pub struct Umem128(u128)
254);
255
256integer_mem!(
257    /// An isize that is also in the value range of a usize.
258    pub struct Imem(isize)
259);
260
261integer_diff!(
262    /// An i8 that is also in the value range of an isize.
263    pub struct Idiff8(i8)
264);
265
266integer_diff!(
267    /// A u8 that is also in the value range of an isize.
268    pub struct Udiff8(u8)
269);
270
271integer_diff!(
272    /// An i16 that is also in the value range of an isize.
273    pub struct Idiff16(i16)
274);
275
276integer_diff!(
277    /// A u16 that is also in the value range of an isize.
278    pub struct Udiff16(u16)
279);
280
281integer_diff!(
282    /// An i32 that is also in the value range of an isize.
283    pub struct Idiff32(i32)
284);
285
286integer_diff!(
287    /// A u32 that is also in the value range of an isize.
288    pub struct Udiff32(u32)
289);
290
291integer_diff!(
292    /// An i64 that is also in the value range of an isize.
293    pub struct Idiff64(i64)
294);
295
296integer_diff!(
297    /// A u64 that is also in the value range of an isize.
298    pub struct Udiff64(u64)
299);
300
301integer_diff!(
302    /// A usize that is also in the value range of an isize.
303    pub struct Udiff(usize)
304);
305
306integer_diff!(
307    /// A i128 that is also in the value range of an isize.
308    pub struct Idiff128(i128)
309);
310
311integer_diff!(
312    /// A u128 that is also in the value range of an isize.
313    pub struct Udiff128(u128)
314);
315
316#[test]
317fn mem_128_operations() {
318    let x = Umem128::new(16).unwrap();
319    // Test: refl-`Eq`.
320    assert!(x == x);
321    // Test: refl-`Ord`.
322    assert!(x <= x);
323
324    // Test: `Ord` for underlying type.
325    assert!(x <= 16u128);
326    assert!(16u128 <= x);
327    // Test: `Eq` for underlying type.
328    assert!(x == 16u128);
329    assert!(16u128 == x);
330
331    // Test: `Ord` for usize.
332    assert!(x <= 16usize);
333    assert!(16usize <= x);
334    // Test: `Eq` for usize.
335    assert!(x == 16usize);
336    assert!(16usize == x);
337}
338
339#[cfg(feature = "alloc")]
340#[test]
341fn fmt_128() {
342    let x = Umem128::new(16).unwrap();
343    assert!(alloc::format!("{x}") == "16");
344}