Skip to main content

mz_ore/
cast.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16// Identity casts (e.g., `u8 as u8`) are generated by the `cast_from!` macro
17// for same-type pairs to provide `const fn` wrappers.
18#![allow(trivial_numeric_casts)]
19
20//! Cast utilities.
21
22/// A trait for safe, simple, and infallible casts.
23///
24/// `CastFrom` is like [`std::convert::From`], but it is implemented for some
25/// platform-specific casts that are missing from the standard library. For
26/// example, there is no `From<u32> for usize` implementation, because Rust may
27/// someday support platforms where usize is smaller than 32 bits. Since we
28/// don't care about such platforms, we are happy to provide a `CastFrom<u32>
29/// for usize` implementation.
30///
31/// `CastFrom` should be preferred to the `as` operator, since the `as` operator
32/// will silently truncate if the target type is smaller than the source type.
33/// When applicable, `CastFrom` should also be preferred to the
34/// [`std::convert::TryFrom`] trait, as `TryFrom` will produce a runtime error,
35/// while `CastFrom` will produce a compile-time error.
36pub trait CastFrom<T> {
37    /// Performs the cast.
38    fn cast_from(from: T) -> Self;
39}
40
41/// The inverse of [`CastFrom`].
42/// Implemented automatically, just like [`std::convert::Into`].
43pub trait CastInto<T> {
44    /// Performs the cast.
45    fn cast_into(self) -> T;
46}
47
48impl<T, U> CastInto<U> for T
49where
50    U: CastFrom<T>,
51{
52    fn cast_into(self) -> U {
53        U::cast_from(self)
54    }
55}
56
57macro_rules! cast_from {
58    ($from:ty, $to:ty) => {
59        paste::paste! {
60            impl crate::cast::CastFrom<$from> for $to {
61                #[allow(clippy::as_conversions)]
62                #[inline(always)]
63                fn cast_from(from: $from) -> $to {
64                    from as $to
65                }
66            }
67
68            /// Casts `from` to `to`.
69            ///
70            /// This is equivalent to the [`crate::cast::CastFrom`] implementation but is
71            /// available as a `const fn`.
72            #[allow(clippy::as_conversions)]
73            pub const fn [< $from _to_ $to >](from: $from) -> $to {
74                from as $to
75            }
76
77            impl crate::cast::CastFrom<std::num::NonZero<$from>> for $to {
78                #[allow(clippy::as_conversions)]
79                #[inline(always)]
80                fn cast_from(from: std::num::NonZero<$from>) -> $to {
81                    from.get() as $to
82                }
83            }
84        }
85    };
86}
87
88#[cfg(target_pointer_width = "32")]
89/// Safe casts for 32bit platforms
90mod target32 {
91    // size_of<from> < size_of<target>
92    cast_from!(u8, usize);
93    cast_from!(u16, usize);
94    cast_from!(u8, isize);
95    cast_from!(i8, isize);
96    cast_from!(u16, isize);
97    cast_from!(i16, isize);
98
99    cast_from!(usize, u64);
100    cast_from!(usize, i64);
101    cast_from!(usize, u128);
102    cast_from!(usize, i128);
103    cast_from!(isize, i64);
104    cast_from!(isize, i128);
105
106    // size_of<from> == size_of<target>
107    cast_from!(usize, u32);
108    cast_from!(isize, i32);
109    cast_from!(u32, usize);
110    cast_from!(i32, isize);
111}
112#[cfg(target_pointer_width = "32")]
113pub use target32::*;
114
115#[cfg(target_pointer_width = "64")]
116/// Safe casts for 64bit platforms
117pub mod target64 {
118    // size_of<from> < size_of<target>
119    cast_from!(u8, usize);
120    cast_from!(u16, usize);
121    cast_from!(u32, usize);
122    cast_from!(u8, isize);
123    cast_from!(i8, isize);
124    cast_from!(u16, isize);
125    cast_from!(i16, isize);
126    cast_from!(u32, isize);
127    cast_from!(i32, isize);
128
129    cast_from!(usize, u128);
130    cast_from!(usize, i128);
131    cast_from!(isize, i128);
132
133    // size_of<from> == size_of<target>
134    cast_from!(usize, u64);
135    cast_from!(isize, i64);
136    cast_from!(u64, usize);
137    cast_from!(i64, isize);
138}
139#[cfg(target_pointer_width = "64")]
140pub use target64::*;
141
142// TODO(petrosagg): remove these once the std From impls become const
143cast_from!(u8, u8);
144cast_from!(u8, u16);
145cast_from!(u8, i16);
146cast_from!(u8, u32);
147cast_from!(u8, i32);
148cast_from!(u8, u64);
149cast_from!(u8, i64);
150cast_from!(u8, u128);
151cast_from!(u8, i128);
152cast_from!(u16, u16);
153cast_from!(u16, u32);
154cast_from!(u16, i32);
155cast_from!(u16, u64);
156cast_from!(u16, i64);
157cast_from!(u16, u128);
158cast_from!(u16, i128);
159cast_from!(u32, u32);
160cast_from!(u32, u64);
161cast_from!(u32, i64);
162cast_from!(u32, u128);
163cast_from!(u32, i128);
164cast_from!(u64, u64);
165cast_from!(u64, u128);
166cast_from!(u64, i128);
167cast_from!(i8, i8);
168cast_from!(i8, i16);
169cast_from!(i8, i32);
170cast_from!(i8, i64);
171cast_from!(i8, i128);
172cast_from!(i16, i16);
173cast_from!(i16, i32);
174cast_from!(i16, i64);
175cast_from!(i16, i128);
176cast_from!(i32, i32);
177cast_from!(i32, i64);
178cast_from!(i32, i128);
179cast_from!(i64, i64);
180cast_from!(i64, i128);
181
182/// A trait for reinterpreting casts.
183///
184/// `ReinterpretCast` is like `as`, but it allows the caller to be specific about their
185/// intentions to reinterpreting the bytes from one type to another. For example, if we
186/// have some `u32` that we want to use as the return value of a postgres function, and
187/// we don't mind converting large unsigned numbers to negative signed numbers, then
188/// we would use `ReinterpretCast<i32>`.
189///
190/// `ReinterpretCast` should be preferred to the `as` operator, since it explicitly
191/// conveys the intention to reinterpret the type.
192pub trait ReinterpretCast<T> {
193    /// Performs the cast.
194    fn reinterpret_cast(from: T) -> Self;
195}
196
197macro_rules! reinterpret_cast {
198    ($from:ty, $to:ty) => {
199        impl ReinterpretCast<$from> for $to {
200            #[allow(clippy::as_conversions)]
201            fn reinterpret_cast(from: $from) -> $to {
202                from as $to
203            }
204        }
205    };
206}
207
208reinterpret_cast!(u8, i8);
209reinterpret_cast!(i8, u8);
210reinterpret_cast!(u16, i16);
211reinterpret_cast!(i16, u16);
212reinterpret_cast!(u32, i32);
213reinterpret_cast!(i32, u32);
214reinterpret_cast!(u64, i64);
215reinterpret_cast!(i64, u64);
216
217/// A trait for attempted casts.
218///
219/// `TryCast` is like `as`, but returns `None` if
220/// the conversion can't be round-tripped.
221///
222/// Note: there may be holes in the domain of `try_cast_from`,
223/// which is probably why `TryFrom` wasn't implemented for floats in the
224/// standard library. For example, `i64::MAX` can be converted to
225/// `f64`, but `i64::MAX - 1` can't.
226pub trait TryCastFrom<T>: Sized {
227    /// Attempts to perform the cast
228    fn try_cast_from(from: T) -> Option<Self>;
229}
230
231/// Implement `TryCastFrom` for the specified types.
232/// This is only necessary for types for which `as` exists,
233/// but `TryFrom` doesn't (notably floats).
234macro_rules! try_cast_from {
235    ($from:ty, $to:ty) => {
236        impl crate::cast::TryCastFrom<$from> for $to {
237            #[allow(clippy::as_conversions)]
238            fn try_cast_from(from: $from) -> Option<$to> {
239                let to = from as $to;
240                let inverse = to as $from;
241                if from == inverse { Some(to) } else { None }
242            }
243        }
244    };
245}
246
247try_cast_from!(f64, i64);
248try_cast_from!(i64, f64);
249try_cast_from!(f64, u64);
250try_cast_from!(u64, f64);
251
252/// A trait for potentially-lossy casts. Typically useful when converting from integers
253/// to floating point, and you want the nearest floating-point number to your integer
254/// when your integer is large, or vice versa.
255pub trait CastLossy<T> {
256    /// Perform the lossy cast.
257    fn cast_lossy(from: T) -> Self;
258}
259
260/// Implement `CastLossy` for the specified types.
261macro_rules! cast_lossy {
262    ($from:ty, $to:ty) => {
263        impl crate::cast::CastLossy<$from> for $to {
264            #[allow(clippy::as_conversions)]
265            fn cast_lossy(from: $from) -> $to {
266                from as $to
267            }
268        }
269    };
270}
271
272cast_lossy!(usize, f32);
273cast_lossy!(isize, f32);
274cast_lossy!(f32, usize);
275cast_lossy!(i64, f32);
276cast_lossy!(f32, i64);
277cast_lossy!(u64, f32);
278cast_lossy!(f32, u64);
279cast_lossy!(f32, u32);
280cast_lossy!(usize, f64);
281cast_lossy!(isize, f64);
282cast_lossy!(f64, usize);
283cast_lossy!(i64, f64);
284cast_lossy!(f64, i64);
285cast_lossy!(u64, f64);
286cast_lossy!(f64, u64);
287cast_lossy!(f64, u32);
288cast_lossy!(i128, f64);
289cast_lossy!(f64, f32);
290
291#[crate::test]
292fn test_try_cast_from() {
293    let f64_i64_cases = vec![
294        (0.0, Some(0)),
295        (1.0, Some(1)),
296        (1.5, None),
297        (f64::INFINITY, None),
298        (f64::NAN, None),
299        (f64::EPSILON, None),
300        (f64::MAX, None),
301        (f64::MIN, None),
302        (9223372036854775807f64, Some(i64::MAX)),
303        (-9223372036854775808f64, Some(i64::MIN)),
304        (9223372036854775807f64 + 10_000f64, None),
305        (-9223372036854775808f64 - 10_000f64, None),
306    ];
307    let i64_f64_cases = vec![
308        (0, Some(0.0)),
309        (1, Some(1.0)),
310        (-1, Some(-1.0)),
311        (i64::MAX, Some(9223372036854775807f64)),
312        (i64::MIN, Some(-9223372036854775808f64)),
313        (i64::MAX - 1, None),
314        (i64::MIN + 1, None),
315    ];
316    let f64_u64_cases = vec![
317        (0.0, Some(0)),
318        (1.0, Some(1)),
319        (1.5, None),
320        (f64::INFINITY, None),
321        (f64::NAN, None),
322        (f64::EPSILON, None),
323        (f64::MAX, None),
324        (f64::MIN, None),
325        (-1.0, None),
326        (18446744073709551615f64, Some(u64::MAX)),
327        (18446744073709551615f64 + 10_000f64, None),
328        // 2^53
329        (9007199254740992f64, Some(9007199254740992)),
330        // 2^53 - 1
331        (9007199254740991f64, Some(9007199254740991)),
332    ];
333    let u64_f64_cases = vec![
334        (0, Some(0.0)),
335        (1, Some(1.0)),
336        (u64::MAX, Some(18446744073709551615f64)),
337        (u64::MAX - 1, None),
338        // 2^53
339        (9007199254740992, Some(9007199254740992f64)),
340        // 2^53 - 1
341        (9007199254740991, Some(9007199254740991f64)),
342        // 2^53 + 1
343        (9007199254740993, None),
344    ];
345    for (f, expect) in f64_i64_cases {
346        let r = i64::try_cast_from(f);
347        assert_eq!(r, expect, "input: {f}");
348    }
349    for (i, expect) in i64_f64_cases {
350        let r = f64::try_cast_from(i);
351        assert_eq!(r, expect, "input: {i}");
352    }
353    for (f, expect) in f64_u64_cases {
354        let r = u64::try_cast_from(f);
355        assert_eq!(r, expect, "input: {f}");
356    }
357    for (u, expect) in u64_f64_cases {
358        let r = f64::try_cast_from(u);
359        assert_eq!(r, expect, "input: {u}");
360    }
361}