Skip to main content

usize_conv/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3#![allow(clippy::doc_markdown)]
4#![doc = include_str!("../README.md")]
5// All casts are validated at compile time via `assert_infallible_cast!`
6// and fallible conversions are guarded against being built on incompatible
7// targets.
8#![allow(clippy::cast_possible_truncation)]
9#![allow(clippy::cast_possible_wrap)]
10
11#[cfg(any(feature = "min-usize-32", feature = "from_usize"))]
12macro_rules! assert_infallible_cast {
13    ($src:tt => $dst:tt) => {
14        const _: () = {
15            const SRC_BITS: u32 = $src::BITS;
16            const DST_BITS: u32 = $dst::BITS;
17            #[allow(clippy::cast_sign_loss)]
18            const SRC_SIGNED: bool = (-1_i8 as $src) < (0 as $src);
19            #[allow(clippy::cast_sign_loss)]
20            const DST_SIGNED: bool = (-1_i8 as $dst) < (0 as $dst);
21            assert!(match (SRC_SIGNED, DST_SIGNED) {
22                (false, false) => SRC_BITS <= DST_BITS,
23                (true, true) => SRC_BITS <= DST_BITS,
24                (false, true) => SRC_BITS < DST_BITS,
25                (true, false) => false,
26            });
27        };
28    };
29}
30
31#[cfg(feature = "min-usize-32")]
32/// Infallibly convert a value into `usize`.
33///
34/// Implementations exist only when the conversion is guaranteed not
35/// to lose information under the selected `min-usize-*` portability
36/// contract.
37///
38/// ```
39/// use usize_conv::ToUsize;
40///
41/// let x: u16 = 5;
42/// assert_eq!(x.to_usize(), 5usize);
43/// ```
44pub trait ToUsize {
45    fn to_usize(self) -> usize;
46}
47
48#[cfg(feature = "min-usize-32")]
49/// Infallibly convert a value into `isize`.
50///
51/// Implementations exist only when the conversion is guaranteed not
52/// to lose information under the selected `min-usize-*` portability
53/// contract.
54///
55/// ```
56/// use usize_conv::ToIsize;
57///
58/// let x: i16 = -3;
59/// assert_eq!(x.to_isize(), -3isize);
60/// ```
61pub trait ToIsize {
62    fn to_isize(self) -> isize;
63}
64
65#[cfg(feature = "from_usize")]
66/// Infallibly convert `usize` to `u64`.
67pub trait ToU64 {
68    fn to_u64(self) -> u64;
69}
70
71#[cfg(feature = "from_usize")]
72/// Infallibly convert `isize` to `i64`.
73pub trait ToI64 {
74    fn to_i64(self) -> i64;
75}
76
77#[cfg(feature = "from_usize")]
78/// Infallibly convert `usize` to `u128`.
79pub trait ToU128 {
80    fn to_u128(self) -> u128;
81}
82
83#[cfg(feature = "from_usize")]
84/// Infallibly convert `usize` or `isize` to `i128`.
85pub trait ToI128 {
86    fn to_i128(self) -> i128;
87}
88
89#[cfg(feature = "min-usize-32")]
90macro_rules! impl_to_usize {
91    ($src:tt) => {
92        assert_infallible_cast!($src => usize);
93
94        impl ToUsize for $src {
95            #[inline]
96            fn to_usize(self) -> usize {
97                self as usize
98            }
99        }
100    };
101}
102
103#[cfg(feature = "min-usize-32")]
104macro_rules! impl_to_isize {
105    ($src:tt) => {
106        assert_infallible_cast!($src => isize);
107
108        impl ToIsize for $src {
109            #[inline]
110            fn to_isize(self) -> isize {
111                self as isize
112            }
113        }
114    };
115}
116
117#[cfg(feature = "min-usize-32")]
118macro_rules! impl_nonzero_usize {
119    ($($src:tt),* $(,)?) => {
120        $(
121            impl ToUsize for $src {
122                #[inline]
123                fn to_usize(self) -> usize {
124                    self.get() as usize
125                }
126            }
127        )*
128    };
129}
130
131#[cfg(feature = "min-usize-32")]
132macro_rules! impl_nonzero_isize {
133    ($($src:ty),* $(,)?) => {
134        $(
135            impl ToIsize for $src {
136                #[inline]
137                fn to_isize(self) -> isize {
138                    self.get() as isize
139                }
140            }
141        )*
142    };
143}
144
145#[cfg(feature = "min-usize-32")]
146impl ToUsize for usize {
147    #[inline]
148    fn to_usize(self) -> usize {
149        self
150    }
151}
152
153#[cfg(feature = "min-usize-32")]
154impl ToIsize for isize {
155    #[inline]
156    fn to_isize(self) -> isize {
157        self
158    }
159}
160
161/// Define infallible conversions to `usize`/`isize` that are guaranteed
162/// on 32-bit or larger architectures
163///
164/// This module is enabled for `min-usize-32` and `min-usize-64`.
165#[cfg(feature = "min-usize-32")]
166mod ge32 {
167    #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
168    compile_error!("`min-usize-32` requires a target with at least 32-bit `usize`");
169
170    #[allow(clippy::wildcard_imports)]
171    use super::*;
172    use core::num::{NonZeroI8, NonZeroI16, NonZeroI32, NonZeroU8, NonZeroU16, NonZeroU32};
173
174    impl_to_usize!(u8);
175    impl_to_usize!(u16);
176    impl_to_usize!(u32);
177
178    impl_to_isize!(u8);
179    impl_to_isize!(i8);
180    impl_to_isize!(u16);
181    impl_to_isize!(i16);
182    impl_to_isize!(i32);
183
184    impl_nonzero_usize!(NonZeroU8);
185    impl_nonzero_usize!(NonZeroU16);
186    impl_nonzero_usize!(NonZeroU32);
187
188    impl_nonzero_isize!(NonZeroU8);
189    impl_nonzero_isize!(NonZeroI8);
190    impl_nonzero_isize!(NonZeroU16);
191    impl_nonzero_isize!(NonZeroI16);
192    impl_nonzero_isize!(NonZeroI32);
193}
194
195/// Define infallible conversions to `usize`/`isize` that are guaranteed on
196/// 64-bit architectures
197///
198/// This module is enabled only for `min-usize-64`.
199#[cfg(feature = "min-usize-64")]
200mod ge64 {
201    #[cfg(not(target_pointer_width = "64"))]
202    compile_error!("`min-usize-64` requires a target with 64-bit `usize`");
203
204    #[allow(clippy::wildcard_imports)]
205    use super::*;
206    use core::num::{NonZeroI64, NonZeroU32, NonZeroU64};
207
208    impl_to_isize!(u32);
209    impl_to_usize!(u64);
210    impl_to_isize!(i64);
211
212    impl_nonzero_usize!(NonZeroU64);
213    impl_nonzero_isize!(NonZeroU32);
214    impl_nonzero_isize!(NonZeroI64);
215}
216
217/// Define widening conversions whose source is `usize`/`isize`.
218///
219/// This module is enabled by the `from_usize` feature.
220#[cfg(feature = "from_usize")]
221mod from_usize_mod {
222    #[allow(clippy::wildcard_imports)]
223    use super::*;
224
225    assert_infallible_cast!(usize => u64);
226
227    impl ToU64 for usize {
228        #[inline]
229        fn to_u64(self) -> u64 {
230            self as u64
231        }
232    }
233
234    assert_infallible_cast!(usize => u128);
235
236    impl ToU128 for usize {
237        #[inline]
238        fn to_u128(self) -> u128 {
239            self as u128
240        }
241    }
242
243    assert_infallible_cast!(usize => i128);
244
245    impl ToI128 for usize {
246        #[inline]
247        fn to_i128(self) -> i128 {
248            self as i128
249        }
250    }
251
252    assert_infallible_cast!(isize => i64);
253
254    impl ToI64 for isize {
255        #[inline]
256        fn to_i64(self) -> i64 {
257            self as i64
258        }
259    }
260
261    assert_infallible_cast!(isize => i128);
262
263    impl ToI128 for isize {
264        #[inline]
265        fn to_i128(self) -> i128 {
266            self as i128
267        }
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    #[allow(clippy::wildcard_imports)]
274    use super::*;
275
276    #[cfg(feature = "min-usize-32")]
277    mod ge32 {
278        #[allow(clippy::wildcard_imports)]
279        use super::*;
280        use core::num::{NonZeroI8, NonZeroI16, NonZeroI32, NonZeroU8, NonZeroU16, NonZeroU32};
281
282        #[test]
283        fn identity() {
284            assert_eq!(7usize.to_usize(), 7);
285            assert_eq!((-7isize).to_isize(), -7);
286        }
287
288        #[test]
289        fn basic() {
290            // u8/i8
291            assert_eq!(123u8.to_usize(), 123);
292            assert_eq!(123u8.to_isize(), 123);
293            assert_eq!((-12i8).to_isize(), -12);
294
295            // u16/i16
296            assert_eq!(123u16.to_usize(), 123);
297            assert_eq!(123u16.to_isize(), 123);
298            assert_eq!((-12i16).to_isize(), -12);
299
300            // u32/i32
301            assert_eq!(456u32.to_usize(), 456);
302            assert_eq!((-456i32).to_isize(), -456);
303        }
304
305        #[test]
306        fn nonzero() {
307            // to usize
308            let x = NonZeroU8::new(5).unwrap();
309            assert_eq!(x.to_usize(), 5);
310
311            let x = NonZeroU16::new(5).unwrap();
312            assert_eq!(x.to_usize(), 5);
313
314            let x = NonZeroU32::new(7).unwrap();
315            assert_eq!(x.to_usize(), 7);
316
317            // to isize (signed)
318            let y = NonZeroI8::new(-5).unwrap();
319            assert_eq!(y.to_isize(), -5);
320
321            let y = NonZeroI16::new(-5).unwrap();
322            assert_eq!(y.to_isize(), -5);
323
324            let y = NonZeroI32::new(-7).unwrap();
325            assert_eq!(y.to_isize(), -7);
326
327            // unsigned to isize (widening cross-sign)
328            let x = NonZeroU8::new(5).unwrap();
329            assert_eq!(x.to_isize(), 5);
330
331            let x = NonZeroU16::new(5).unwrap();
332            assert_eq!(x.to_isize(), 5);
333        }
334    }
335
336    #[cfg(feature = "min-usize-64")]
337    mod ge64 {
338        #[allow(clippy::wildcard_imports)]
339        use super::*;
340        use core::num::{NonZeroI64, NonZeroU32, NonZeroU64};
341
342        #[test]
343        fn basic() {
344            // unsigned to usize
345            assert_eq!(789u64.to_usize(), 789);
346
347            // signed to isize
348            assert_eq!((-789i64).to_isize(), -789);
349
350            // u32 -> isize: only valid on 64-bit where isize is wider than u32
351            assert_eq!(456u32.to_isize(), 456);
352        }
353
354        #[test]
355        fn nonzero() {
356            let x = NonZeroU64::new(9).unwrap();
357            assert_eq!(x.to_usize(), 9);
358
359            let y = NonZeroI64::new(-9).unwrap();
360            assert_eq!(y.to_isize(), -9);
361
362            // u32 -> isize: only valid on 64-bit
363            let x = NonZeroU32::new(7).unwrap();
364            assert_eq!(x.to_isize(), 7);
365        }
366    }
367
368    #[cfg(feature = "from_usize")]
369    #[test]
370    fn widening() {
371        assert_eq!(42usize.to_u64(), 42);
372        assert_eq!(42usize.to_u128(), 42);
373        assert_eq!(42usize.to_i128(), 42);
374
375        assert_eq!((-42isize).to_i64(), -42);
376        assert_eq!((-42isize).to_i128(), -42);
377    }
378}