Skip to main content

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::{Nearest, traits::*};
11//!
12//! fn nth_power<X: CastApprox<f64>>(x: X, n: u32) {
13//!     let x = x.cast_approx();    // Into-like approximate conversion
14//!
15//!     let power = i32::conv(n);  // From-like exact conversion
16//!     let z = x.powi(power);
17//!     println!("The {n}-th power of {x} is {z}");
18//!
19//!     // TryFrom-like approximate (nearest) conversion
20//!     if let Ok(nearest) = isize::try_conv_to(Nearest, z) {
21//!         println!("Nearest integer: {nearest}");
22//!     }
23//! }
24//! ```
25//!
26
27use crate::{Approx, Error, Exact, RangeError, Rounding};
28#[allow(unused)]
29use core::convert::Infallible;
30
31/// Generic "from" conversion trait for exact conversions
32///
33/// This trait is provided as an implementation aid only, hence there is no
34/// `CastExact` (in most cases you can just use [`Cast`]).
35///
36/// ## Implementing exact conversions
37///
38/// Implement conversions which cannot lose precision using this trait.
39/// Implementations of <code>[ConvTo]&lt;S, R&gt;</code> are implied for all
40/// <code>R: [Rounding]</code> modes provided by this crate (see
41/// [§ Implied implementations](Rounding#implied-implementations)).
42///
43/// ### Example
44///
45/// ```
46/// use easy_cast::ConvExact;
47/// use std::convert::Infallible;
48///
49/// struct MyBigInt { /* details */ }
50///
51/// // Support conversion from i32:
52/// impl ConvExact<i32> for MyBigInt {
53///     type Error = Infallible;
54///
55///     fn try_conv_exact(i: i32) -> Result<Self, Infallible> {
56///         Ok(todo!())
57///     }
58///
59///     // optionally also impl fn conv_exact
60/// }
61/// ```
62///
63/// Note that in practice you'll probably want to support conversion from many
64/// integer types using `macro_rules!`. Or you could "cheat" with a generic
65/// `impl<S: Into<i128>> ConvExact<S> for MyBigInt { ... }`.
66//
67// TODO(specialization): impl<T> ConvExact<T> for T
68pub trait ConvExact<S>: Sized {
69    /// Conversion error type
70    ///
71    /// This should be either [`Infallible`] or [`RangeError`].
72    type Error: Into<RangeError> + Into<crate::Error> + core::error::Error;
73
74    /// Try converting from `S` to `Self`
75    fn try_conv_exact(s: S) -> Result<Self, Self::Error>;
76
77    /// Convert from `S` to `Self`
78    ///
79    /// Use this method only when success is expected. On error, this method may
80    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
81    ///
82    /// # Implementing
83    ///
84    /// Implementing this method directly (with fallback behaviour) is optional.
85    /// In debug builds, this method must panic on error.
86    #[inline]
87    fn conv_exact(s: S) -> Self {
88        Self::try_conv_exact(s).unwrap_or_else(|e| {
89            panic!("ConvExact::conv_exact(_) failed: {}", e);
90        })
91    }
92}
93
94/// Like [`From`], but supports fallible conversions
95///
96/// This trait has similarities to [`From`] and [`TryFrom`], but is limited to
97/// numeric conversions:
98/// -   Conversions may be *fallible*, like [`TryFrom`].
99/// -   Conversions must be *lossless*, like [`From`]; this corresponds to the
100///     [`Exact`] "rounding" mode. (See also [`ConvApprox`].)
101/// -   Conversions must be *value-preserving*, like [`From`]. For example,
102///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
103///     conceptually a different value.
104///
105/// The sister-trait [`Cast`] supports "into" style usage.
106///
107/// This trait should not be implemented directly; instead implement either
108/// [`ConvExact`] or [`ConvTo`].
109pub trait Conv<S>: Sized {
110    /// Conversion error type
111    ///
112    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
113    type Error: Into<Error> + core::error::Error;
114
115    /// Try converting from `S` to `Self`
116    fn try_conv(s: S) -> Result<Self, Self::Error>;
117
118    /// Convert from `S` to `Self`
119    ///
120    /// Use this method only when success is expected. On error, this method may
121    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
122    fn conv(s: S) -> Self {
123        Self::try_conv(s).unwrap_or_else(|e| {
124            panic!("Conv::conv(_) failed: {}", e);
125        })
126    }
127}
128
129impl<S, T: ConvTo<S, Exact>> Conv<S> for T {
130    type Error = T::Error;
131
132    #[inline]
133    fn try_conv(s: S) -> Result<Self, Self::Error> {
134        T::try_conv_to(Exact, s)
135    }
136
137    #[inline]
138    fn conv(s: S) -> Self {
139        T::conv_to(Exact, s)
140    }
141}
142
143/// Like [`Into`], but for [`Conv`]
144///
145/// This trait has similarities to [`Into`] and [`TryInto`], but limited to
146/// numeric conversions:
147/// -   Conversions may be *fallible*, like [`TryInto`].
148/// -   Conversions must be *lossless*, like [`Into`]; this corresponds to the
149///     [`Exact`] "rounding" mode. (See also [`CastApprox`].)
150/// -   Conversions must be *value-preserving*, like [`Into`]. For example,
151///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
152///     conceptually a different value.
153///
154/// This trait is automatically implemented for every implementation of
155/// [`Conv`].
156pub trait Cast<T> {
157    /// Conversion error type
158    ///
159    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
160    type Error: Into<Error> + core::error::Error;
161
162    /// Try converting from `Self` to `T`
163    fn try_cast(self) -> Result<T, Self::Error>;
164
165    /// Cast from `Self` to `T`
166    ///
167    /// Use this method only when success is expected. On error, this method may
168    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
169    ///
170    /// Note: the unstable `float_conversions` feature adds an inherent `cast`
171    /// method to float types which in some cases will conflict with usage of
172    /// this method. We recommend using `Cast::cast(x)` instead of `x.cast()`
173    /// as a work-around until this feature stabilizes.
174    fn cast(self) -> T;
175}
176
177impl<S, T: Conv<S>> Cast<T> for S {
178    type Error = T::Error;
179
180    #[inline]
181    fn cast(self) -> T {
182        T::conv(self)
183    }
184    #[inline]
185    fn try_cast(self) -> Result<T, Self::Error> {
186        T::try_conv(self)
187    }
188}
189
190/// Like [`From`], but for approximate numerical conversions
191///
192/// This trait supports [`From`]- and [`TryFrom`]-like conversions, but allowing
193/// approximation:
194/// -   Conversions may be *fallible*, like [`TryFrom`].
195/// -   Conversions may be *lossy*, provided that the result is close to the
196///     input value (see
197///     [`§ Limits of approximation`](Approx#limits-of-approximation)).
198/// -   Conversions must be *value-preserving*, like [`From`]. For example,
199///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
200///     conceptually a different value.
201///
202/// The rounding mode used is implementation-defined. Conversions provided by
203/// this crate use the same behaviour as [`as` numeric casts]: float-to-int
204/// conversions round towards zero while conversions to floating-point formats
205/// produce the closest possible float (rounding ties to even).
206/// Use [`ConvTo`] where specific rounding is required.
207///
208/// The sister-trait [`CastApprox`] supports "into" style usage.
209///
210/// This trait should not be implemented directly; instead implement [`ConvTo`]
211/// using the [`Approx`] rounding mode.
212///
213/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
214pub trait ConvApprox<S>: Sized {
215    /// Conversion error type
216    ///
217    /// This should be either [`Infallible`] or [`RangeError`].
218    type Error: Into<RangeError> + core::error::Error;
219
220    /// Try converting from `S` to `Self`, allowing approximation
221    fn try_conv_approx(s: S) -> Result<Self, Self::Error>;
222
223    /// Convert from `S` to `Self`, allowing approximation
224    ///
225    /// Use this method only when success is expected. On error, this method may
226    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
227    #[inline]
228    fn conv_approx(s: S) -> Self {
229        Self::try_conv_approx(s).unwrap_or_else(|e| {
230            panic!("ConvApprox::conv_approx(_) failed: {}", e);
231        })
232    }
233}
234
235impl<S, T: ConvTo<S, Approx>> ConvApprox<S> for T {
236    type Error = T::Error;
237
238    #[inline]
239    fn try_conv_approx(s: S) -> Result<Self, Self::Error> {
240        T::try_conv_to(Approx, s)
241    }
242
243    #[inline]
244    fn conv_approx(s: S) -> Self {
245        T::conv_to(Approx, s)
246    }
247}
248
249/// Like [`Into`], but for [`ConvApprox`]
250///
251/// This trait supports [`Into`]- and [`TryInto`]-like conversions, but allowing
252/// approximation:
253/// -   Conversions may be *fallible*, like [`TryInto`].
254/// -   Conversions may be *lossy*, provided that the result is close to the
255///     input value (see
256///     [`§ Limits of approximation`](Approx#limits-of-approximation)).
257/// -   Conversions must be *value-preserving*, like [`Into`]. For example,
258///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
259///     conceptually a different value.
260///
261/// The rounding mode used is implementation-defined. Conversions provided by
262/// this crate use the same behaviour as [`as` numeric casts]: float-to-int
263/// conversions round towards zero while conversions to floating-point formats
264/// produce the closest possible float (rounding ties to even).
265/// Use [`CastTo`] where specific rounding is required.
266///
267/// This trait is automatically implemented for every implementation of
268/// [`ConvApprox`].
269///
270/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
271pub trait CastApprox<T> {
272    /// Conversion error type
273    ///
274    /// This should be either [`Infallible`] or [`RangeError`].
275    type Error: Into<RangeError> + core::error::Error;
276
277    /// Try approximate conversion from `Self` to `T`
278    fn try_cast_approx(self) -> Result<T, Self::Error>;
279
280    /// Cast approximately from `Self` to `T`
281    ///
282    /// Use this method only when success is expected. On error, this method may
283    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
284    fn cast_approx(self) -> T;
285}
286
287impl<S, T: ConvApprox<S>> CastApprox<T> for S {
288    type Error = T::Error;
289
290    #[inline]
291    fn try_cast_approx(self) -> Result<T, Self::Error> {
292        T::try_conv_approx(self)
293    }
294    #[inline]
295    fn cast_approx(self) -> T {
296        T::conv_approx(self)
297    }
298}
299
300/// Generic "from" conversion trait with specified rounding mode
301///
302/// This trait supports [`From`]- and [`TryFrom`]-like conversions, but with a
303/// specified rounding mode:
304/// -   Conversions may be *fallible*, like [`TryFrom`].
305/// -   Conversions may be *lossy*, according to the [`Rounding`] mode used.
306/// -   Conversions must be *value-preserving*, like [`From`]. For example,
307///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
308///     conceptually a different value.
309///
310/// The [`Rounding`] mode must be specified:
311/// ```
312/// # use easy_cast::{ConvTo, Exact, Nearest};
313/// assert_eq!(i32::conv_to(Nearest, 7.6f32), 8);
314/// assert_eq!(f32::conv_to(Exact, 20), 20.0);
315/// ```
316/// Usage with [`Exact`] and [`Approx`] is equivalent to usage of [`Conv`] and
317/// [`ConvApprox`] respectively.
318///
319/// The sister-trait [`CastTo`] supports "into" style usage.
320///
321/// ## Implementing conversions
322///
323/// Implement conversions which cannot lose precision using [`ConvExact`] and
324/// other conversions using this trait. Separate implementations may be provided
325/// for each [`Rounding`] mode.
326///
327/// ### Example
328///
329/// ```
330/// use easy_cast::{Approx, ConvTo, Error, Exact, RangeError};
331///
332/// struct MyFloat { /* details */ }
333/// # impl MyFloat {
334/// #   fn is_in_range_of<T>(&self) -> bool { todo!() }
335/// #   fn is_integral(&self) -> bool { todo!() }
336/// # }
337///
338/// impl ConvTo<MyFloat, Exact> for i32 {
339///     type Error = Error;
340///
341///     fn try_conv_to(_: Exact, f: MyFloat) -> Result<Self, Self::Error> {
342///         if f.is_in_range_of::<i32>() {
343///             if f.is_integral() {
344///                 Ok(todo!())
345///             } else {
346///                 Err(Error::Inexact)
347///             }
348///         } else {
349///             Err(Error::Range)
350///         }
351///     }
352///
353///     // optionally also impl fn conv
354/// }
355///
356/// impl ConvTo<MyFloat, Approx> for i32 {
357///     type Error = RangeError;
358///
359///     fn try_conv_to(_: Approx, f: MyFloat) -> Result<Self, Self::Error> {
360///         if f.is_in_range_of::<i32>() {
361///             Ok(todo!())
362///         } else {
363///             Err(RangeError)
364///         }
365///     }
366///
367///     // optionally also impl fn conv
368/// }
369///
370/// // optionally also implement ConvTo for other rounding modes
371/// ```
372pub trait ConvTo<S, R: Rounding>: Sized {
373    /// Conversion error type
374    ///
375    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
376    type Error: Into<R::MaximumError> + core::error::Error;
377
378    /// Try converting from `S` to `Self`, rounding according to `mode`
379    fn try_conv_to(mode: R, s: S) -> Result<Self, Self::Error>;
380
381    /// Convert from `S` to `Self`, rounding according to `mode`
382    ///
383    /// Use this method only when success is expected. On error, this method may
384    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
385    ///
386    /// # Implementing
387    ///
388    /// Implementing this method directly (with fallback behaviour) is optional.
389    /// In debug builds, this method must panic on error.
390    fn conv_to(mode: R, s: S) -> Self {
391        Self::try_conv_to(mode, s).unwrap_or_else(|e| panic!("ConvTo::conv_to(_) failed: {e}"))
392    }
393}
394
395/// Generic "into" conversion trait with specified rounding mode
396///
397/// This trait supports [`Into`]- and [`TryInto`]-like conversions, but with a
398/// specified rounding mode:
399/// -   Conversions may be *fallible*, like [`TryInto`].
400/// -   Conversions may be *lossy*, according to the [`Rounding`] mode used.
401/// -   Conversions must be *value-preserving*, like [`Into`]. For example,
402///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
403///     conceptually a different value.
404///
405/// The [`Rounding`] mode must be specified:
406/// ```
407/// # use easy_cast::{CastTo, Floor, Nearest};
408/// let x: i32 = 3.14192.cast_to(Floor);
409/// assert_eq!(x, 3);
410///
411/// let y = (1i32 << 30) - 1;
412/// let z: f32 = y.cast_to(Nearest);  // this example rounds up
413/// assert_eq!(z as i32, 1i32 << 30);
414/// ```
415/// Usage with [`Exact`] and [`Approx`] is equivalent to usage of [`Cast`] and
416/// [`CastApprox`] respectively.
417///
418/// This trait is automatically implemented for every implementation of [`ConvTo`].
419pub trait CastTo<T, R: Rounding>: Sized {
420    /// Conversion error type
421    ///
422    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
423    type Error: Into<R::MaximumError> + core::error::Error;
424
425    /// Try converting from `Self` to `T`, rounding according to `mode`
426    fn try_cast_to(self, mode: R) -> Result<T, Self::Error>;
427
428    /// Convert from `Self` to `T`, rounding according to `mode`
429    ///
430    /// Use this method only when success is expected. On error, this method may
431    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
432    fn cast_to(self, mode: R) -> T;
433}
434
435impl<R: Rounding, S, T: ConvTo<S, R>> CastTo<T, R> for S {
436    type Error = T::Error;
437
438    fn try_cast_to(self, mode: R) -> Result<T, Self::Error> {
439        T::try_conv_to(mode, self)
440    }
441
442    fn cast_to(self, mode: R) -> T {
443        T::conv_to(mode, self)
444    }
445}