num_conv/lib.rs
1//! `num_conv` is a crate to convert between integer types without using `as` casts. This provides
2//! better certainty when refactoring, makes the exact behavior of code more explicit, and allows
3//! using turbofish syntax.
4
5#![no_std]
6
7/// Anonymously import all extension traits.
8///
9/// This allows you to use the methods without worrying about polluting the namespace or importing
10/// them individually.
11///
12/// ```rust
13/// use num_conv::prelude::*;
14/// ```
15pub mod prelude {
16 pub use crate::{CastSigned as _, CastUnsigned as _, Extend as _, Truncate as _};
17}
18
19mod sealed {
20 pub trait Integer {}
21
22 macro_rules! impl_integer {
23 ($($t:ty)*) => {$(
24 impl Integer for $t {}
25 )*};
26 }
27
28 impl_integer! {
29 u8 u16 u32 u64 u128 usize
30 i8 i16 i32 i64 i128 isize
31 }
32
33 pub trait ExtendTargetSealed<T> {
34 fn extend(self) -> T;
35 }
36
37 pub trait TruncateTargetSealed<T> {
38 fn truncate(self) -> T;
39 }
40}
41
42/// Cast to a signed integer of the same size.
43///
44/// This trait is implemented for all integers. Unsigned to signed casts are equivalent to
45/// `0.wrapping_add_signed(value)`, while signed to signed casts are an identity conversion.
46///
47/// ```rust
48/// # use num_conv::CastSigned;
49/// assert_eq!(u8::MAX.cast_signed(), -1_i8);
50/// assert_eq!(u16::MAX.cast_signed(), -1_i16);
51/// assert_eq!(u32::MAX.cast_signed(), -1_i32);
52/// assert_eq!(u64::MAX.cast_signed(), -1_i64);
53/// assert_eq!(u128::MAX.cast_signed(), -1_i128);
54/// assert_eq!(usize::MAX.cast_signed(), -1_isize);
55/// ```
56///
57/// ```rust
58/// # use num_conv::CastSigned;
59/// assert_eq!(0_i8.cast_signed(), 0_i8);
60/// assert_eq!(0_i16.cast_signed(), 0_i16);
61/// assert_eq!(0_i32.cast_signed(), 0_i32);
62/// assert_eq!(0_i64.cast_signed(), 0_i64);
63/// assert_eq!(0_i128.cast_signed(), 0_i128);
64/// assert_eq!(0_isize.cast_signed(), 0_isize);
65/// ```
66pub trait CastSigned: sealed::Integer {
67 /// The signed integer type with the same size as `Self`.
68 type Signed;
69
70 /// Cast an integer to the signed integer of the same size.
71 fn cast_signed(self) -> Self::Signed;
72}
73
74/// Cast to an unsigned integer of the same size.
75///
76/// This trait is implemented for all integers. Signed to unsigned casts are equivalent to
77/// `0.wrapping_add_unsigned(value)`, while unsigned to unsigned casts are an identity conversion.
78///
79/// ```rust
80/// # use num_conv::CastUnsigned;
81/// assert_eq!((-1_i8).cast_unsigned(), u8::MAX);
82/// assert_eq!((-1_i16).cast_unsigned(), u16::MAX);
83/// assert_eq!((-1_i32).cast_unsigned(), u32::MAX);
84/// assert_eq!((-1_i64).cast_unsigned(), u64::MAX);
85/// assert_eq!((-1_i128).cast_unsigned(), u128::MAX);
86/// assert_eq!((-1_isize).cast_unsigned(), usize::MAX);
87/// ```
88///
89/// ```rust
90/// # use num_conv::CastUnsigned;
91/// assert_eq!(0_u8.cast_unsigned(), 0_u8);
92/// assert_eq!(0_u16.cast_unsigned(), 0_u16);
93/// assert_eq!(0_u32.cast_unsigned(), 0_u32);
94/// assert_eq!(0_u64.cast_unsigned(), 0_u64);
95/// assert_eq!(0_u128.cast_unsigned(), 0_u128);
96/// assert_eq!(0_usize.cast_unsigned(), 0_usize);
97/// ```
98pub trait CastUnsigned: sealed::Integer {
99 /// The unsigned integer type with the same size as `Self`.
100 type Unsigned;
101
102 /// Cast an integer to the unsigned integer of the same size.
103 fn cast_unsigned(self) -> Self::Unsigned;
104}
105
106/// A type that can be used with turbofish syntax in [`Extend::extend`].
107///
108/// It is unlikely that you will want to use this trait directly. You are probably looking for the
109/// [`Extend`] trait.
110pub trait ExtendTarget<T>: sealed::ExtendTargetSealed<T> {}
111
112/// A type that can be used with turbofish syntax in [`Truncate::truncate`].
113///
114/// It is unlikely that you will want to use this trait directly. You are probably looking for the
115/// [`Truncate`] trait.
116pub trait TruncateTarget<T>: sealed::TruncateTargetSealed<T> {}
117
118/// Extend to an integer of the same size or larger, preserving its value.
119///
120/// ```rust
121/// # use num_conv::Extend;
122/// assert_eq!(0_u8.extend::<u16>(), 0_u16);
123/// assert_eq!(0_u16.extend::<u32>(), 0_u32);
124/// assert_eq!(0_u32.extend::<u64>(), 0_u64);
125/// assert_eq!(0_u64.extend::<u128>(), 0_u128);
126/// ```
127///
128/// ```rust
129/// # use num_conv::Extend;
130/// assert_eq!((-1_i8).extend::<i16>(), -1_i16);
131/// assert_eq!((-1_i16).extend::<i32>(), -1_i32);
132/// assert_eq!((-1_i32).extend::<i64>(), -1_i64);
133/// assert_eq!((-1_i64).extend::<i128>(), -1_i128);
134/// ```
135pub trait Extend: sealed::Integer {
136 /// Extend an integer to an integer of the same size or larger, preserving its value.
137 fn extend<T>(self) -> T
138 where
139 Self: ExtendTarget<T>;
140}
141
142impl<T: sealed::Integer> Extend for T {
143 fn extend<U>(self) -> U
144 where
145 T: ExtendTarget<U>,
146 {
147 sealed::ExtendTargetSealed::extend(self)
148 }
149}
150
151/// Truncate to an integer of the same size or smaller, preserving the least significant bits.
152///
153/// ```rust
154/// # use num_conv::Truncate;
155/// assert_eq!(u16::MAX.truncate::<u8>(), u8::MAX);
156/// assert_eq!(u32::MAX.truncate::<u16>(), u16::MAX);
157/// assert_eq!(u64::MAX.truncate::<u32>(), u32::MAX);
158/// assert_eq!(u128::MAX.truncate::<u64>(), u64::MAX);
159/// ```
160///
161/// ```rust
162/// # use num_conv::Truncate;
163/// assert_eq!((-1_i16).truncate::<i8>(), -1_i8);
164/// assert_eq!((-1_i32).truncate::<i16>(), -1_i16);
165/// assert_eq!((-1_i64).truncate::<i32>(), -1_i32);
166/// assert_eq!((-1_i128).truncate::<i64>(), -1_i64);
167/// ```
168pub trait Truncate: sealed::Integer {
169 /// Truncate an integer to an integer of the same size or smaller, preserving the least
170 /// significant bits.
171 fn truncate<T>(self) -> T
172 where
173 Self: TruncateTarget<T>;
174}
175
176impl<T: sealed::Integer> Truncate for T {
177 fn truncate<U>(self) -> U
178 where
179 T: TruncateTarget<U>,
180 {
181 sealed::TruncateTargetSealed::truncate(self)
182 }
183}
184
185macro_rules! impl_cast_signed {
186 ($($($from:ty),+ => $to:ty;)*) => {$($(
187 const _: () = assert!(
188 core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
189 concat!(
190 "cannot cast ",
191 stringify!($from),
192 " to ",
193 stringify!($to),
194 " because they are different sizes"
195 )
196 );
197
198 impl CastSigned for $from {
199 type Signed = $to;
200 fn cast_signed(self) -> Self::Signed {
201 self as _
202 }
203 }
204 )+)*};
205}
206
207macro_rules! impl_cast_unsigned {
208 ($($($from:ty),+ => $to:ty;)*) => {$($(
209 const _: () = assert!(
210 core::mem::size_of::<$from>() == core::mem::size_of::<$to>(),
211 concat!(
212 "cannot cast ",
213 stringify!($from),
214 " to ",
215 stringify!($to),
216 " because they are different sizes"
217 )
218 );
219
220 impl CastUnsigned for $from {
221 type Unsigned = $to;
222 fn cast_unsigned(self) -> Self::Unsigned {
223 self as _
224 }
225 }
226 )+)*};
227}
228
229macro_rules! impl_extend {
230 ($($from:ty => $($to:ty),+;)*) => {$($(
231 const _: () = assert!(
232 core::mem::size_of::<$from>() <= core::mem::size_of::<$to>(),
233 concat!(
234 "cannot extend ",
235 stringify!($from),
236 " to ",
237 stringify!($to),
238 " because ",
239 stringify!($from),
240 " is larger than ",
241 stringify!($to)
242 )
243 );
244
245 impl sealed::ExtendTargetSealed<$to> for $from {
246 fn extend(self) -> $to {
247 self as _
248 }
249 }
250
251 impl ExtendTarget<$to> for $from {}
252 )+)*};
253}
254
255macro_rules! impl_truncate {
256 ($($($from:ty),+ => $to:ty;)*) => {$($(
257 const _: () = assert!(
258 core::mem::size_of::<$from>() >= core::mem::size_of::<$to>(),
259 concat!(
260 "cannot truncate ",
261 stringify!($from),
262 " to ",
263 stringify!($to),
264 " because ",
265 stringify!($from),
266 " is smaller than ",
267 stringify!($to)
268 )
269 );
270
271 impl sealed::TruncateTargetSealed<$to> for $from {
272 fn truncate(self) -> $to {
273 self as _
274 }
275 }
276
277 impl TruncateTarget<$to> for $from {}
278 )+)*};
279}
280
281impl_cast_signed! {
282 u8, i8 => i8;
283 u16, i16 => i16;
284 u32, i32 => i32;
285 u64, i64 => i64;
286 u128, i128 => i128;
287 usize, isize => isize;
288}
289
290impl_cast_unsigned! {
291 u8, i8 => u8;
292 u16, i16 => u16;
293 u32, i32 => u32;
294 u64, i64 => u64;
295 u128, i128 => u128;
296 usize, isize => usize;
297}
298
299impl_extend! {
300 u8 => u8, u16, u32, u64, u128, usize;
301 u16 => u16, u32, u64, u128, usize;
302 u32 => u32, u64, u128;
303 u64 => u64, u128;
304 u128 => u128;
305 usize => usize;
306
307 i8 => i8, i16, i32, i64, i128, isize;
308 i16 => i16, i32, i64, i128, isize;
309 i32 => i32, i64, i128;
310 i64 => i64, i128;
311 i128 => i128;
312 isize => isize;
313}
314
315impl_truncate! {
316 u8, u16, u32, u64, u128, usize => u8;
317 u16, u32, u64, u128, usize => u16;
318 u32, u64, u128 => u32;
319 u64, u128 => u64;
320 u128 => u128;
321 usize => usize;
322
323 i8, i16, i32, i64, i128, isize => i8;
324 i16, i32, i64, i128, isize => i16;
325 i32, i64, i128 => i32;
326 i64, i128 => i64;
327 i128 => i128;
328 isize => isize;
329}