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::{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 fn saturating_truncate(self) -> T;
40 fn checked_truncate(self) -> Option<T>;
41 }
42}
43
44/// A type that can be used with turbofish syntax in [`Extend::extend`].
45///
46/// It is unlikely that you will want to use this trait directly. You are probably looking for the
47/// [`Extend`] trait.
48pub trait ExtendTarget<T>: sealed::ExtendTargetSealed<T> {}
49
50/// A type that can be used with turbofish syntax in [`Truncate::truncate`].
51///
52/// It is unlikely that you will want to use this trait directly. You are probably looking for the
53/// [`Truncate`] trait.
54pub trait TruncateTarget<T>: sealed::TruncateTargetSealed<T> {}
55
56/// Extend to an integer of the same size or larger, preserving its value.
57///
58/// ```rust
59/// # use num_conv::Extend;
60/// assert_eq!(0_u8.extend::<u16>(), 0_u16);
61/// assert_eq!(0_u16.extend::<u32>(), 0_u32);
62/// assert_eq!(0_u32.extend::<u64>(), 0_u64);
63/// assert_eq!(0_u64.extend::<u128>(), 0_u128);
64/// ```
65///
66/// ```rust
67/// # use num_conv::Extend;
68/// assert_eq!((-1_i8).extend::<i16>(), -1_i16);
69/// assert_eq!((-1_i16).extend::<i32>(), -1_i32);
70/// assert_eq!((-1_i32).extend::<i64>(), -1_i64);
71/// assert_eq!((-1_i64).extend::<i128>(), -1_i128);
72/// ```
73pub trait Extend: sealed::Integer {
74 /// Extend an integer to an integer of the same size or larger, preserving its value.
75 fn extend<T>(self) -> T
76 where
77 Self: ExtendTarget<T>;
78}
79
80impl<T: sealed::Integer> Extend for T {
81 fn extend<U>(self) -> U
82 where
83 T: ExtendTarget<U>,
84 {
85 sealed::ExtendTargetSealed::extend(self)
86 }
87}
88
89/// Truncate to an integer of the same size or smaller.
90///
91/// Preserve the least significant bits with `.truncate()`:
92///
93/// ```rust
94/// # use num_conv::Truncate;
95/// assert_eq!(u16::MAX.truncate::<u8>(), u8::MAX);
96/// assert_eq!(u32::MAX.truncate::<u16>(), u16::MAX);
97/// assert_eq!(u64::MAX.truncate::<u32>(), u32::MAX);
98/// assert_eq!(u128::MAX.truncate::<u64>(), u64::MAX);
99/// ```
100///
101/// ```rust
102/// # use num_conv::Truncate;
103/// assert_eq!((-1_i16).truncate::<i8>(), -1_i8);
104/// assert_eq!((-1_i32).truncate::<i16>(), -1_i16);
105/// assert_eq!((-1_i64).truncate::<i32>(), -1_i32);
106/// assert_eq!((-1_i128).truncate::<i64>(), -1_i64);
107/// ```
108///
109/// Saturate to the numeric bounds with `.saturating_truncate()`:
110///
111/// ```rust
112/// # use num_conv::Truncate;
113/// assert_eq!(500_u16.saturating_truncate::<u8>(), u8::MAX);
114/// assert_eq!(u32::MAX.saturating_truncate::<u16>(), u16::MAX);
115/// assert_eq!(u64::MAX.saturating_truncate::<u32>(), u32::MAX);
116/// assert_eq!(u128::MAX.saturating_truncate::<u64>(), u64::MAX);
117/// ```
118///
119/// ```rust
120/// # use num_conv::Truncate;
121/// assert_eq!((-500_i16).saturating_truncate::<i8>(), i8::MIN);
122/// assert_eq!(i32::MIN.saturating_truncate::<i16>(), i16::MIN);
123/// assert_eq!(i64::MIN.saturating_truncate::<i32>(), i32::MIN);
124/// assert_eq!(i128::MIN.saturating_truncate::<i64>(), i64::MIN);
125/// ```
126///
127/// Checked with `.checked_truncate()`, returning `None` if the value is out of range:
128///
129/// ```rust
130/// # use num_conv::Truncate;
131/// assert_eq!(u16::MAX.checked_truncate::<u8>(), None);
132/// assert_eq!(u32::MAX.checked_truncate::<u16>(), None);
133/// assert_eq!(u64::MAX.checked_truncate::<u32>(), None);
134/// assert_eq!(u128::MAX.checked_truncate::<u64>(), None);
135/// ```
136///
137/// ```rust
138/// # use num_conv::Truncate;
139/// assert_eq!(i16::MIN.checked_truncate::<i8>(), None);
140/// assert_eq!(i32::MIN.checked_truncate::<i16>(), None);
141/// assert_eq!(i64::MIN.checked_truncate::<i32>(), None);
142/// assert_eq!(i128::MIN.checked_truncate::<i64>(), None);
143/// ```
144pub trait Truncate: sealed::Integer {
145 /// Truncate an integer to an integer of the same size or smaller, preserving the least
146 /// significant bits.
147 fn truncate<T>(self) -> T
148 where
149 Self: TruncateTarget<T>;
150
151 /// Truncate an integer to an integer of the same size or smaller, saturating to the numeric
152 /// bounds instead of wrapping.
153 fn saturating_truncate<T>(self) -> T
154 where
155 Self: TruncateTarget<T>;
156
157 /// Truncate an integer to an integer of the same size or smaller, returning `None` if the value
158 /// is out of range.
159 fn checked_truncate<T>(self) -> Option<T>
160 where
161 Self: TruncateTarget<T>;
162}
163
164impl<T: sealed::Integer> Truncate for T {
165 fn truncate<U>(self) -> U
166 where
167 T: TruncateTarget<U>,
168 {
169 sealed::TruncateTargetSealed::truncate(self)
170 }
171
172 fn saturating_truncate<U>(self) -> U
173 where
174 T: TruncateTarget<U>,
175 {
176 sealed::TruncateTargetSealed::saturating_truncate(self)
177 }
178
179 fn checked_truncate<U>(self) -> Option<U>
180 where
181 T: TruncateTarget<U>,
182 {
183 sealed::TruncateTargetSealed::checked_truncate(self)
184 }
185}
186
187macro_rules! impl_extend {
188 ($($from:ty => $($to:ty),+;)*) => {$($(
189 const _: () = assert!(
190 core::mem::size_of::<$from>() <= core::mem::size_of::<$to>(),
191 concat!(
192 "cannot extend ",
193 stringify!($from),
194 " to ",
195 stringify!($to),
196 " because ",
197 stringify!($from),
198 " is larger than ",
199 stringify!($to)
200 )
201 );
202
203 impl sealed::ExtendTargetSealed<$to> for $from {
204 #[inline]
205 fn extend(self) -> $to {
206 self as _
207 }
208 }
209
210 impl ExtendTarget<$to> for $from {}
211 )+)*};
212}
213
214macro_rules! impl_truncate {
215 ($($($from:ty),+ => $to:ty;)*) => {$($(
216 const _: () = assert!(
217 core::mem::size_of::<$from>() >= core::mem::size_of::<$to>(),
218 concat!(
219 "cannot truncate ",
220 stringify!($from),
221 " to ",
222 stringify!($to),
223 " because ",
224 stringify!($from),
225 " is smaller than ",
226 stringify!($to)
227 )
228 );
229
230 impl sealed::TruncateTargetSealed<$to> for $from {
231 #[inline]
232 fn truncate(self) -> $to {
233 self as _
234 }
235
236 #[inline]
237 fn saturating_truncate(self) -> $to {
238 if self > <$to>::MAX as _ {
239 <$to>::MAX
240 } else if self < <$to>::MIN as _ {
241 <$to>::MIN
242 } else {
243 self as _
244 }
245 }
246
247 #[inline]
248 fn checked_truncate(self) -> Option<$to> {
249 if self > <$to>::MAX as _ || self < <$to>::MIN as _ {
250 None
251 } else {
252 Some(self as _)
253 }
254 }
255 }
256
257 impl TruncateTarget<$to> for $from {}
258 )+)*};
259}
260
261impl_extend! {
262 u8 => u8, u16, u32, u64, u128, usize;
263 u16 => u16, u32, u64, u128, usize;
264 u32 => u32, u64, u128;
265 u64 => u64, u128;
266 u128 => u128;
267 usize => usize;
268
269 i8 => i8, i16, i32, i64, i128, isize;
270 i16 => i16, i32, i64, i128, isize;
271 i32 => i32, i64, i128;
272 i64 => i64, i128;
273 i128 => i128;
274 isize => isize;
275}
276
277impl_truncate! {
278 u8, u16, u32, u64, u128, usize => u8;
279 u16, u32, u64, u128, usize => u16;
280 u32, u64, u128 => u32;
281 u64, u128 => u64;
282 u128 => u128;
283 usize => usize;
284
285 i8, i16, i32, i64, i128, isize => i8;
286 i16, i32, i64, i128, isize => i16;
287 i32, i64, i128 => i32;
288 i64, i128 => i64;
289 i128 => i128;
290 isize => isize;
291}