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}