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}