easy_cast/
traits.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Traits
7//!
8//! This module only contains traits, allowing relatively safe glob-import:
9//! ```
10//! use easy_cast::traits::*;
11//!
12//! # fn main() {
13//! let x = i32::conv_nearest(8.5);
14//! let y: f32 = 12.cast();
15//! # }
16//! ```
17
18use super::Result;
19
20/// Like [`From`], but supports fallible conversions
21///
22/// This trait is intented to be an extension over [`From`], also supporting
23/// fallible conversions of numeric types.
24/// Since Rust does not yet have stable support for handling conflicting
25/// implementations (specialization or otherwise), only conversions between
26/// the most important numeric types are supported for now.
27///
28/// The sister-trait [`Cast`] supports "into" style usage.
29pub trait Conv<T>: Sized {
30    /// Try converting from `T` to `Self`
31    ///
32    /// This method must fail on inexact conversions.
33    fn try_conv(v: T) -> Result<Self>;
34
35    /// Convert from `T` to `Self`
36    ///
37    /// This method must return the same result as [`Self::try_conv`] where that
38    /// method succeeds, but differs in the handling of errors:
39    ///
40    /// -   In debug builds the method panics on error
41    /// -   Otherwise, the method may panic or may return a different value,
42    ///     but like with the `as` keyword all results must be well-defined and
43    ///     *safe*.
44    ///
45    /// Default implementations use [`Self::try_conv`] and panic on error.
46    /// Implementations provided by this library will panic in debug builds
47    /// or if the `always_assert` feature flag is used, and otherwise will
48    /// behave identically to the `as` keyword.
49    ///
50    /// This mirrors the behaviour of Rust's overflow checks on integer
51    /// arithmetic in that it is a tool for diagnosing logic errors where
52    /// success is expected.
53    fn conv(v: T) -> Self {
54        Self::try_conv(v).unwrap_or_else(|e| {
55            panic!("Conv::conv(_) failed: {}", e);
56        })
57    }
58}
59
60/// Like [`Into`], but for [`Conv`]
61///
62/// This trait is automatically implemented for every implementation of
63/// [`Conv`].
64pub trait Cast<T> {
65    /// Try converting from `Self` to `T`
66    ///
67    /// Use this method to explicitly handle errors.
68    fn try_cast(self) -> Result<T>;
69
70    /// Cast from `Self` to `T`
71    ///
72    /// Use this method *only* where success is expected: implementations are
73    /// permitted to panic or silently return a different (safe, defined) value
74    /// on error.
75    ///
76    /// In debug builds, implementations must panic.
77    ///
78    /// Implementations by this library will panic in debug builds or if the
79    /// `always_assert` feature flag is used, otherwise conversions have the
80    /// same behaviour as the `as` keyword.
81    fn cast(self) -> T;
82}
83
84impl<S, T: Conv<S>> Cast<T> for S {
85    #[inline]
86    fn cast(self) -> T {
87        T::conv(self)
88    }
89    #[inline]
90    fn try_cast(self) -> Result<T> {
91        T::try_conv(self)
92    }
93}
94
95/// Like [`From`], but for approximate numerical conversions
96///
97/// On success, the result must be approximately the same as the input value:
98/// the difference must be smaller than the precision of the target type.
99/// For example, one may have `i32::conv_approx(1.9f32) = 1` or
100/// `f32::conv_approx(1f64 + (f32::EPSILON as f64) / 2.0) = 1.0`.
101///
102/// Precise rounding mode should usually be truncation (round towards zero),
103/// but this is not required. Use [`ConvFloat`] where a specific rounding mode
104/// is required.
105///
106/// The sister-trait [`CastApprox`] supports "into" style usage.
107pub trait ConvApprox<T>: Sized {
108    /// Try converting from `T` to `Self`, allowing approximation of value
109    ///
110    /// This conversion may truncate excess precision not supported by the
111    /// target type, so long as the *value* is approximately equal, from the
112    /// point of view of precision of the target type.
113    ///
114    /// This method should allow approximate conversion, but fail on input not
115    /// (approximately) in the target's range.
116    fn try_conv_approx(x: T) -> Result<Self>;
117
118    /// Converting from `T` to `Self`, allowing approximation of value
119    ///
120    /// This method must return the same result as [`Self::try_conv_approx`]
121    /// where that method succeeds, but differs in the handling of errors:
122    ///
123    /// -   In debug builds the method panics on error
124    /// -   Otherwise, the method may panic or may return a different value,
125    ///     but like with the `as` keyword all results must be well-defined and
126    ///     *safe*.
127    ///
128    /// Default implementations use [`Self::try_conv_approx`] and panic on error.
129    /// Implementations provided by this library will panic in debug builds
130    /// or if the `always_assert` feature flag is used, and otherwise will
131    /// behave identically to the `as` keyword.
132    ///
133    /// This mirrors the behaviour of Rust's overflow checks on integer
134    /// arithmetic in that it is a tool for diagnosing logic errors where
135    /// success is expected.
136    #[inline]
137    fn conv_approx(x: T) -> Self {
138        Self::try_conv_approx(x).unwrap_or_else(|e| {
139            panic!("ConvApprox::conv_approx(_) failed: {}", e);
140        })
141    }
142}
143
144// TODO(specialization): implement also where T: ConvFloat<S>
145impl<S, T: Conv<S>> ConvApprox<S> for T {
146    #[inline]
147    fn try_conv_approx(x: S) -> Result<Self> {
148        T::try_conv(x)
149    }
150    #[inline]
151    fn conv_approx(x: S) -> Self {
152        T::conv(x)
153    }
154}
155
156/// Like [`Into`], but for [`ConvApprox`]
157///
158/// On success, the result must be approximately the same as the input value:
159/// the difference must be smaller than the precision of the target type.
160/// For example, one may have `1.9f32.cast_approx() = 1`.
161///
162/// Precise rounding mode should usually be truncation (round towards zero),
163/// but this is not required. Use [`CastFloat`] where a specific rounding mode
164/// is required.
165///
166/// This trait is automatically implemented for every implementation of
167/// [`ConvApprox`].
168pub trait CastApprox<T> {
169    /// Try approximate conversion from `Self` to `T`
170    ///
171    /// Use this method to explicitly handle errors.
172    fn try_cast_approx(self) -> Result<T>;
173
174    /// Cast approximately from `Self` to `T`
175    ///
176    /// Use this method *only* where success is expected: implementations are
177    /// permitted to panic or silently return a different (safe, defined) value
178    /// on error.
179    ///
180    /// In debug builds, implementations must panic.
181    ///
182    /// Implementations by this library will panic in debug builds or if the
183    /// `always_assert` feature flag is used, otherwise conversions have the
184    /// same behaviour as the `as` keyword.
185    fn cast_approx(self) -> T;
186}
187
188impl<S, T: ConvApprox<S>> CastApprox<T> for S {
189    #[inline]
190    fn try_cast_approx(self) -> Result<T> {
191        T::try_conv_approx(self)
192    }
193    #[inline]
194    fn cast_approx(self) -> T {
195        T::conv_approx(self)
196    }
197}
198
199/// Nearest / floor / ceiling conversions from floating point types
200///
201/// This trait is explicitly for conversions from floating-point values to
202/// integers, supporting four rounding modes.
203///
204/// As with [`Conv`], the `try_conv_*` methods must be implemented and must fail
205/// if conversion to the expected value is not possible. If the source is non-
206/// finite (`inf` or `NaN`), then `Error::Range` should be returned.
207///
208/// The `conv_*` methods each have a default implementation over the `try_..`
209/// variant which panics on failure. Implementations handle errors as follows:
210///
211/// -   In debug builds, the methods must panic
212/// -   Otherwise, the method may panic or may return a different value; all
213///     results must be well-defined and *safe*.
214/// -   Implementations provided by this library will also panic if the
215///     `always_assert` or `assert_float` feature flag is used.
216///
217/// The sister-trait [`CastFloat`] supports "into" style usage.
218#[cfg(any(feature = "std", feature = "libm"))]
219pub trait ConvFloat<T>: Sized {
220    /// Try converting to integer with truncation
221    ///
222    /// Rounds towards zero (same as `as`).
223    fn try_conv_trunc(x: T) -> Result<Self>;
224    /// Try converting to the nearest integer
225    ///
226    /// Half-way cases are rounded away from `0`.
227    fn try_conv_nearest(x: T) -> Result<Self>;
228    /// Try converting the floor to an integer
229    ///
230    /// Returns the largest integer less than or equal to `x`.
231    fn try_conv_floor(x: T) -> Result<Self>;
232    /// Try convert the ceiling to an integer
233    ///
234    /// Returns the smallest integer greater than or equal to `x`.
235    fn try_conv_ceil(x: T) -> Result<Self>;
236
237    /// Convert to integer with truncatation
238    ///
239    /// Rounds towards zero (same as `as`).
240    fn conv_trunc(x: T) -> Self {
241        Self::try_conv_trunc(x).unwrap_or_else(|e| panic!("ConvFloat::conv_trunc(_) failed: {}", e))
242    }
243    /// Convert to the nearest integer
244    ///
245    /// Half-way cases are rounded away from `0`.
246    fn conv_nearest(x: T) -> Self {
247        Self::try_conv_nearest(x)
248            .unwrap_or_else(|e| panic!("ConvFloat::conv_nearest(_) failed: {}", e))
249    }
250    /// Convert the floor to an integer
251    ///
252    /// Returns the largest integer less than or equal to `x`.
253    fn conv_floor(x: T) -> Self {
254        Self::try_conv_floor(x).unwrap_or_else(|e| panic!("ConvFloat::conv_floor(_) failed: {}", e))
255    }
256    /// Convert the ceiling to an integer
257    ///
258    /// Returns the smallest integer greater than or equal to `x`.
259    fn conv_ceil(x: T) -> Self {
260        Self::try_conv_ceil(x).unwrap_or_else(|e| panic!("ConvFloat::conv_ceil(_) failed: {}", e))
261    }
262}
263
264/// Like [`Into`], but for [`ConvFloat`]
265///
266/// Use:
267///
268/// -   `try_cast_*` methods to explicitly handle errors
269/// -   `cast_*` methods *only* where success is expected. Implementations are
270///     permitted to panic or silently return a different (safe, defined) value
271///     on error.
272///
273///     In debug builds, implementations must panic.
274///
275///     Implementations by this library will panic in debug builds or if the
276///     `always_assert` or `assert_float` feature flag is used, otherwise
277///     conversions have similar behaviour to the `as` keyword.
278///
279/// This trait is automatically implemented for every implementation of
280/// [`ConvFloat`].
281#[cfg(any(feature = "std", feature = "libm"))]
282pub trait CastFloat<T> {
283    /// Cast to integer, truncating
284    ///
285    /// Rounds towards zero (same as `as`).
286    fn cast_trunc(self) -> T;
287    /// Cast to the nearest integer
288    ///
289    /// Half-way cases are rounded away from `0`.
290    fn cast_nearest(self) -> T;
291    /// Cast the floor to an integer
292    ///
293    /// Returns the largest integer less than or equal to `self`.
294    fn cast_floor(self) -> T;
295    /// Cast the ceiling to an integer
296    ///
297    /// Returns the smallest integer greater than or equal to `self`.
298    fn cast_ceil(self) -> T;
299
300    /// Try converting to integer with truncation
301    ///
302    /// Rounds towards zero (same as `as`).
303    fn try_cast_trunc(self) -> Result<T>;
304    /// Try converting to the nearest integer
305    ///
306    /// Half-way cases are rounded away from `0`.
307    fn try_cast_nearest(self) -> Result<T>;
308    /// Try converting the floor to an integer
309    ///
310    /// Returns the largest integer less than or equal to `x`.
311    fn try_cast_floor(self) -> Result<T>;
312    /// Try convert the ceiling to an integer
313    ///
314    /// Returns the smallest integer greater than or equal to `x`.
315    fn try_cast_ceil(self) -> Result<T>;
316}
317
318#[cfg(any(feature = "std", feature = "libm"))]
319impl<S, T: ConvFloat<S>> CastFloat<T> for S {
320    #[inline]
321    fn cast_trunc(self) -> T {
322        T::conv_trunc(self)
323    }
324    #[inline]
325    fn cast_nearest(self) -> T {
326        T::conv_nearest(self)
327    }
328    #[inline]
329    fn cast_floor(self) -> T {
330        T::conv_floor(self)
331    }
332    #[inline]
333    fn cast_ceil(self) -> T {
334        T::conv_ceil(self)
335    }
336
337    #[inline]
338    fn try_cast_trunc(self) -> Result<T> {
339        T::try_conv_trunc(self)
340    }
341    #[inline]
342    fn try_cast_nearest(self) -> Result<T> {
343        T::try_conv_nearest(self)
344    }
345    #[inline]
346    fn try_cast_floor(self) -> Result<T> {
347        T::try_conv_floor(self)
348    }
349    #[inline]
350    fn try_cast_ceil(self) -> Result<T> {
351        T::try_conv_ceil(self)
352    }
353}