platform_cast/
lib.rs

1#![no_std]
2//! Platform-specific safe cast.
3//!
4//! See [`CastFrom`] for details.
5
6/// Like [`From`] but also support safe casts from/to [`usize`] and [`isize`].
7///
8/// |                           | [`From`] | [`TryFrom`] | [`as`][numeric cast] cast | [`CastFrom`] |
9/// | ------------------------- | -------- | ----------- | ------------------------- | ------------ |
10/// | No runtime check          | ✅       | ❌          | ✅                        | ✅           |
11/// | [`usize`] <-> [`u64`][^a] | ❌       | ✅          | ✅                        | ✅           |
12/// | No truncation             | ✅       | ✅          | ❌                        | ✅           |
13/// | No change of sign         | ✅       | ✅          | ❌                        | ✅           |
14///
15/// [^a]: Assuming a target with a pointer width of 64.
16///
17/// [`From<usize>`] is not implemented for [`u64`] even on platforms
18/// where the pointer width is 64 bit or lower (all currently supported
19/// targets).
20///
21/// Thus the following code fails to compile:
22/// ```compile_fail
23/// let a = usize::MAX;
24/// let b = u64::from(a);
25/// // Error: the trait `From<usize>` is not implemented for `u64`
26/// ```
27///
28/// Instead you have to resort to using [`TryFrom`] and
29/// [`unwrap`](Option::unwrap):
30/// ```
31/// let a = usize::MAX;
32/// let b = u64::try_from(a).unwrap();
33/// ```
34///
35/// or using a potentially truncating/signedness-changing [numeric cast] using
36/// `as`:
37/// ```
38/// let a = usize::MAX;
39/// let b = a as u64;
40/// ```
41///
42/// If you want to guarantee that there are no runtime panics and the value will
43/// not be truncated, you can instead use [`CastFrom`] or [`CastInto`]:
44/// ```
45/// use platform_cast::{CastFrom, CastInto};
46///
47/// let a = usize::MAX;
48/// let b = u64::cast_from(a);
49///
50/// // Alternatively:
51/// let b: u64 = a.cast_into();
52/// ```
53///
54/// The casts available depends on the target platform.
55/// The following code will only compile if the target pointer width is 64 bit
56/// or more.
57/// ```ignore
58/// use platform_cast::CastFrom;
59///
60/// let a = u64::MAX;
61/// let b = usize::cast_from(a);
62/// // On 32-bit targets: error[E0277]: the trait bound `usize: CastFrom<u64>`
63/// is not satisfied
64/// ```
65///
66/// [numeric cast]: https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#r-expr.as.numeric
67pub trait CastFrom<T>: Sized {
68    fn cast_from(value: T) -> Self;
69}
70
71/// Like [`Into`] but also support safe casts from/to [`usize`] and [`isize`].
72///
73/// See [`CastFrom`] for more details.
74pub trait CastInto<T>: Sized {
75    fn cast_into(self) -> T;
76}
77
78impl<T, U> CastInto<U> for T
79where
80    U: CastFrom<T>,
81{
82    fn cast_into(self) -> U {
83        CastFrom::cast_from(self)
84    }
85}
86
87macro_rules! nz {
88    (u8) => {
89        core::num::NonZeroU8
90    };
91    (i8) => {
92        core::num::NonZeroI8
93    };
94    (u16) => {
95        core::num::NonZeroU16
96    };
97    (i16) => {
98        core::num::NonZeroI16
99    };
100    (u32) => {
101        core::num::NonZeroU32
102    };
103    (i32) => {
104        core::num::NonZeroI32
105    };
106    (u64) => {
107        core::num::NonZeroU64
108    };
109    (i64) => {
110        core::num::NonZeroI64
111    };
112    (u128) => {
113        core::num::NonZeroU128
114    };
115    (i128) => {
116        core::num::NonZeroI128
117    };
118    (usize) => {
119        core::num::NonZeroUsize
120    };
121    (isize) => {
122        core::num::NonZeroIsize
123    };
124}
125
126macro_rules! cast_from {
127    ($from:tt, $to:tt) => {
128        const _: () = {
129            let unsigned_from = <$from>::MIN == 0;
130            let unsigned_to = <$to>::MIN == 0;
131            if unsigned_from != unsigned_to {
132                panic!("signedness differ");
133            }
134
135            #[allow(clippy::cast_possible_truncation)]
136            if <$from>::MAX as $to as $from != <$from>::MAX {
137                panic!("from::MAX doesn't fit in to");
138            }
139            #[allow(clippy::cast_possible_truncation)]
140            if <$from>::MIN as $to as $from != <$from>::MIN {
141                panic!("from::MIN doesn't fit in to");
142            }
143        };
144
145        impl super::CastFrom<$from> for $to {
146            #[allow(clippy::cast_possible_truncation)]
147            fn cast_from(from: $from) -> $to {
148                from as $to
149            }
150        }
151
152        impl super::CastFrom<nz!($from)> for nz!($to) {
153            fn cast_from(from: nz!($from)) -> nz!($to) {
154                // SAFETY: As the input is non-zero the output is also non-zero
155                unsafe { <nz!($to)>::new_unchecked(super::CastFrom::cast_from(from.get())) }
156            }
157        }
158    };
159}
160
161#[cfg(target_pointer_width = "16")]
162mod target16 {
163    cast_from!(u8, usize);
164    cast_from!(i8, isize);
165    cast_from!(u16, usize);
166    cast_from!(i16, isize);
167
168    cast_from!(usize, u16);
169    cast_from!(isize, i16);
170    cast_from!(usize, u32);
171    cast_from!(isize, i32);
172    cast_from!(usize, u64);
173    cast_from!(isize, i64);
174    cast_from!(usize, u128);
175    cast_from!(isize, i128);
176}
177
178#[cfg(target_pointer_width = "32")]
179mod target32 {
180    cast_from!(u8, usize);
181    cast_from!(i8, isize);
182    cast_from!(u16, usize);
183    cast_from!(i16, isize);
184    cast_from!(u32, usize);
185    cast_from!(i32, isize);
186
187    cast_from!(usize, u32);
188    cast_from!(isize, i32);
189    cast_from!(usize, u64);
190    cast_from!(isize, i64);
191    cast_from!(usize, u128);
192    cast_from!(isize, i128);
193}
194
195#[cfg(target_pointer_width = "64")]
196mod target64 {
197    cast_from!(u8, usize);
198    cast_from!(i8, isize);
199    cast_from!(u16, usize);
200    cast_from!(i16, isize);
201    cast_from!(u32, usize);
202    cast_from!(i32, isize);
203    cast_from!(u64, usize);
204    cast_from!(i64, isize);
205
206    cast_from!(usize, u64);
207    cast_from!(isize, i64);
208    cast_from!(usize, u128);
209    cast_from!(isize, i128);
210}