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