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}