truncate_integer/
lib.rs

1//! truncate-integer: integer truncation for Rust
2//!
3//! There are several ways one might want to do integer truncation in Rust:
4//!
5//! - Unchecked: truncation may result in a changed value. You only get
6//! the low-order N bits.
7//! - Checked: if truncation would result in a changed value, return `None`,
8//! otherwise `Some(value)`.
9//! - Panicking: if truncation would result in a changed value, 'panic!'
10//! This is equivalent to checked truncation with `.unwrap()`, but with a nicer
11//! panic message.
12//! - Saturating: if truncation would result in a changed value, return
13//! the maximum value that would fit in the target type.
14//!
15//! It's possible to get all of these in Rust without importing additional
16//! crates or writing much code, for example:
17//!
18//! ```rust
19//! # use std::convert::TryFrom;
20//! let x = 257u16;
21//!
22//! let unchecked = x as u8;
23//! assert_eq!(unchecked, 1u8);
24//!
25//! let checked = u8::try_from(x);
26//! assert!(checked.is_err());
27//!
28//! // This would panic
29//! // let value = x.try_from().unwrap();
30//!
31//! let saturating = u8::try_from(x).unwrap_or(u8::MAX);
32//! assert_eq!(saturating, 255);
33//! ```
34//!
35//! If those are good enough for you, then you don't need this crate.
36//! However, if you would prefer to call a function to communicate your
37//! intent, then you might find this crate useful.
38//!
39//! It provides a trait that implements each of the truncation forms listed above:
40//!
41//! - [`TruncateUnchecked`] performs unchecked truncation.
42//! - [`TryTruncate`] performs checked truncation.
43//! - [`Chop`] performs panicking truncation.
44//! - [`Shrink`] performs saturating truncation.
45//!
46//! It's sometimes desirable to invert this logic, e.g. in trait bounds,
47//! so there is an inverse of each of the above:
48//!
49//! - [`TruncateFromUnchecked`]
50//! - [`TryTruncateFrom`]
51//! - [`ChopFrom`]
52//! - [`ShrinkFrom`]
53//!
54//! All of the truncations are implemented for both signed and unsigned
55//! integers (including signed-to-unsigned and vice versa), except
56//! `TruncateFromUnchecked`, because it's not immediately clear what the
57//! correct output would be when then input is outside the output bounds.
58#![no_std]
59
60pub trait TryTruncate<T> {
61    /// Try to truncate an integer to fit into a smaller type.
62    ///
63    /// If the value fits into the target type, return `Ok(value)`
64    /// Otherwise, return `None`.
65    fn try_truncate(self) -> Option<T>;
66}
67
68pub trait TryTruncateFrom<T>: Sized {
69    /// Try to truncate an integer to fit into a smaller type.
70    ///
71    /// If the value fits into the `Self` type, return `Ok(value)`
72    /// Otherwise, return `None`.
73    fn try_truncate_from(value: T) -> Option<Self>;
74}
75
76impl<Source, Dest> TryTruncateFrom<Source> for Dest
77where
78    Source: TryTruncate<Dest>,
79{
80    fn try_truncate_from(x: Source) -> Option<Self> {
81        x.try_truncate()
82    }
83}
84
85pub trait Chop<T> {
86    /// Perform panicking truncation
87    ///
88    /// If the value fits into the target type, return that value.
89    /// Otherwise, panic.
90    fn chop(self) -> T;
91}
92
93pub trait ChopFrom<T> {
94    /// Perform panicking truncation
95    ///
96    /// If the value fits into the `Self` type, return that value.
97    /// Otherwise, panic.
98    fn chop_from(value: T) -> Self;
99}
100
101impl<Source, Dest> ChopFrom<Source> for Dest
102where
103    Source: Chop<Dest>,
104{
105    fn chop_from(x: Source) -> Self {
106        x.chop()
107    }
108}
109
110pub trait TruncateUnchecked<T> {
111    /// Perform unchecked bitwise truncation
112    ///
113    /// If the value fits into the target type, return that value.
114    /// Otherwise, return the low-order bits that do fit.
115    ///
116    /// This has the same result as using `as` to truncate (e.g. `foo as u8`).
117    fn truncate_unchecked(self) -> T;
118}
119
120pub trait TruncateFromUnchecked<T> {
121    /// Perform unchecked bitwise truncation
122    ///
123    /// If the value fits into the `Self` type, return that value.
124    /// Otherwise, return the low-order bits that do fit.
125    ///
126    /// This has the same result as using `as` to truncate (e.g. `foo as u8`).
127    fn truncate_from_unchecked(value: T) -> Self;
128}
129
130impl<Source, Dest> TruncateFromUnchecked<Source> for Dest
131where
132    Source: TruncateUnchecked<Dest>,
133{
134    fn truncate_from_unchecked(x: Source) -> Self {
135        x.truncate_unchecked()
136    }
137}
138
139/// Perform saturating truncation.
140pub trait Shrink<T> {
141    /// Perform saturating truncation.
142    ///
143    /// If the value fits into the target type, return that value.
144    /// Otherwise, return the closest value that does fit.
145    fn shrink(self) -> T;
146}
147
148/// Perform saturating truncation.
149pub trait ShrinkFrom<T> {
150    /// Perform saturating truncation.
151    ///
152    /// If the value fits into the `Self` type, return that value.
153    /// Otherwise, return the closest value that does fit.
154    fn shrink_from(value: T) -> Self;
155}
156
157impl<Source, Dest> ShrinkFrom<Source> for Dest
158where
159    Source: Shrink<Dest>,
160{
161    fn shrink_from(x: Source) -> Self {
162        x.shrink()
163    }
164}
165
166macro_rules! make_truncate {
167    ($Source: ty, $Dest:ty) => {
168        impl TryTruncate<$Dest> for $Source {
169            #[track_caller]
170            #[inline]
171            fn try_truncate(self) -> Option<$Dest> {
172                use ::core::convert::TryFrom;
173                <$Dest>::try_from(self).ok()
174            }
175        }
176
177        impl Chop<$Dest> for $Source {
178            #[track_caller]
179            #[inline]
180            fn chop(self) -> $Dest {
181                use ::core::convert::TryFrom;
182
183                match <$Dest>::try_from(self) {
184                    Ok(val) => val,
185                    Err(_) => panic!("chop overflow"),
186                }
187            }
188        }
189
190        impl Shrink<$Dest> for $Source {
191            #[track_caller]
192            #[inline]
193            fn shrink(self) -> $Dest {
194                use ::core::convert::TryFrom;
195
196                match <$Dest>::try_from(self) {
197                    Ok(val) => val,
198                    Err(_) => {
199                        if self < (<$Dest>::MIN) as $Source {
200                            <$Dest>::MIN
201                        } else {
202                            <$Dest>::MAX
203                        }
204                    }
205                }
206            }
207        }
208
209    };
210}
211
212macro_rules! make_truncate_all {
213    ($Source: ty, $Dest:ty) => {
214        // FIXME: don't implement this for negative numbers!
215        impl TruncateUnchecked<$Dest> for $Source {
216            #[track_caller]
217            #[inline]
218            fn truncate_unchecked(self) -> $Dest {
219                self as $Dest
220            }
221        }
222
223        make_truncate!($Source, $Dest);
224    }
225}
226
227make_truncate_all!(usize, u8);
228make_truncate_all!(usize, u16);
229make_truncate_all!(usize, u32);
230
231make_truncate_all!(u128, u8);
232make_truncate_all!(u128, u16);
233make_truncate_all!(u128, u32);
234make_truncate_all!(u128, u64);
235make_truncate_all!(u64, u8);
236make_truncate_all!(u64, u16);
237make_truncate_all!(u64, u32);
238make_truncate_all!(u32, u8);
239make_truncate_all!(u32, u16);
240make_truncate_all!(u16, u8);
241
242make_truncate_all!(u128, i8);
243make_truncate_all!(u128, i16);
244make_truncate_all!(u128, i32);
245make_truncate_all!(u128, i64);
246make_truncate_all!(u64, i8);
247make_truncate_all!(u64, i16);
248make_truncate_all!(u64, i32);
249make_truncate_all!(u32, i8);
250make_truncate_all!(u32, i16);
251make_truncate_all!(u16, i8);
252
253make_truncate!(i128, i64);
254make_truncate!(i128, i32);
255make_truncate!(i128, i16);
256make_truncate!(i128, i8);
257make_truncate!(i64, i8);
258make_truncate!(i64, i16);
259make_truncate!(i64, i32);
260make_truncate!(i32, i8);
261make_truncate!(i32, i16);
262make_truncate!(i16, i8);
263
264make_truncate!(i128, u64);
265make_truncate!(i128, u32);
266make_truncate!(i128, u16);
267make_truncate!(i128, u8);
268make_truncate!(i64, u8);
269make_truncate!(i64, u16);
270make_truncate!(i64, u32);
271make_truncate!(i32, u8);
272make_truncate!(i32, u16);
273make_truncate!(i16, u8);