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);