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    fn cast(self) -> T;
170}
171
172impl<S, T: Conv<S>> Cast<T> for S {
173    type Error = T::Error;
174
175    #[inline]
176    fn cast(self) -> T {
177        T::conv(self)
178    }
179    #[inline]
180    fn try_cast(self) -> Result<T, Self::Error> {
181        T::try_conv(self)
182    }
183}
184
185/// Like [`From`], but for approximate numerical conversions
186///
187/// This trait supports [`From`]- and [`TryFrom`]-like conversions, but allowing
188/// approximation:
189/// -   Conversions may be *fallible*, like [`TryFrom`].
190/// -   Conversions may be *lossy*, provided that the result is close to the
191///     input value (see
192///     [`§ Limits of approximation`](Approx#limits-of-approximation)).
193/// -   Conversions must be *value-preserving*, like [`From`]. For example,
194///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
195///     conceptually a different value.
196///
197/// The rounding mode used is implementation-defined. Conversions provided by
198/// this crate use the same behaviour as [`as` numeric casts]: float-to-int
199/// conversions round towards zero while conversions to floating-point formats
200/// produce the closest possible float (rounding ties to even).
201/// Use [`ConvTo`] where specific rounding is required.
202///
203/// The sister-trait [`CastApprox`] supports "into" style usage.
204///
205/// This trait should not be implemented directly; instead implement [`ConvTo`]
206/// using the [`Approx`] rounding mode.
207///
208/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
209pub trait ConvApprox<S>: Sized {
210    /// Conversion error type
211    ///
212    /// This should be either [`Infallible`] or [`RangeError`].
213    type Error: Into<RangeError> + core::error::Error;
214
215    /// Try converting from `S` to `Self`, allowing approximation
216    fn try_conv_approx(s: S) -> Result<Self, Self::Error>;
217
218    /// Convert from `S` to `Self`, allowing approximation
219    ///
220    /// Use this method only when success is expected. On error, this method may
221    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
222    #[inline]
223    fn conv_approx(s: S) -> Self {
224        Self::try_conv_approx(s).unwrap_or_else(|e| {
225            panic!("ConvApprox::conv_approx(_) failed: {}", e);
226        })
227    }
228}
229
230impl<S, T: ConvTo<S, Approx>> ConvApprox<S> for T {
231    type Error = T::Error;
232
233    #[inline]
234    fn try_conv_approx(s: S) -> Result<Self, Self::Error> {
235        T::try_conv_to(Approx, s)
236    }
237
238    #[inline]
239    fn conv_approx(s: S) -> Self {
240        T::conv_to(Approx, s)
241    }
242}
243
244/// Like [`Into`], but for [`ConvApprox`]
245///
246/// This trait supports [`Into`]- and [`TryInto`]-like conversions, but allowing
247/// approximation:
248/// -   Conversions may be *fallible*, like [`TryInto`].
249/// -   Conversions may be *lossy*, provided that the result is close to the
250///     input value (see
251///     [`§ Limits of approximation`](Approx#limits-of-approximation)).
252/// -   Conversions must be *value-preserving*, like [`Into`]. For example,
253///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
254///     conceptually a different value.
255///
256/// The rounding mode used is implementation-defined. Conversions provided by
257/// this crate use the same behaviour as [`as` numeric casts]: float-to-int
258/// conversions round towards zero while conversions to floating-point formats
259/// produce the closest possible float (rounding ties to even).
260/// Use [`CastTo`] where specific rounding is required.
261///
262/// This trait is automatically implemented for every implementation of
263/// [`ConvApprox`].
264///
265/// [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
266pub trait CastApprox<T> {
267    /// Conversion error type
268    ///
269    /// This should be either [`Infallible`] or [`RangeError`].
270    type Error: Into<RangeError> + core::error::Error;
271
272    /// Try approximate conversion from `Self` to `T`
273    fn try_cast_approx(self) -> Result<T, Self::Error>;
274
275    /// Cast approximately from `Self` to `T`
276    ///
277    /// Use this method only when success is expected. On error, this method may
278    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
279    fn cast_approx(self) -> T;
280}
281
282impl<S, T: ConvApprox<S>> CastApprox<T> for S {
283    type Error = T::Error;
284
285    #[inline]
286    fn try_cast_approx(self) -> Result<T, Self::Error> {
287        T::try_conv_approx(self)
288    }
289    #[inline]
290    fn cast_approx(self) -> T {
291        T::conv_approx(self)
292    }
293}
294
295/// Generic "from" conversion trait with specified rounding mode
296///
297/// This trait supports [`From`]- and [`TryFrom`]-like conversions, but with a
298/// specified rounding mode:
299/// -   Conversions may be *fallible*, like [`TryFrom`].
300/// -   Conversions may be *lossy*, according to the [`Rounding`] mode used.
301/// -   Conversions must be *value-preserving*, like [`From`]. For example,
302///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
303///     conceptually a different value.
304///
305/// The [`Rounding`] mode must be specified:
306/// ```
307/// # use easy_cast::{ConvTo, Exact, Nearest};
308/// assert_eq!(i32::conv_to(Nearest, 7.6f32), 8);
309/// assert_eq!(f32::conv_to(Exact, 20), 20.0);
310/// ```
311/// Usage with [`Exact`] and [`Approx`] is equivalent to usage of [`Conv`] and
312/// [`ConvApprox`] respectively.
313///
314/// The sister-trait [`CastTo`] supports "into" style usage.
315///
316/// ## Implementing conversions
317///
318/// Implement conversions which cannot lose precision using [`ConvExact`] and
319/// other conversions using this trait. Separate implementations may be provided
320/// for each [`Rounding`] mode.
321///
322/// ### Example
323///
324/// ```
325/// use easy_cast::{Approx, ConvTo, Error, Exact, RangeError};
326///
327/// struct MyFloat { /* details */ }
328/// # impl MyFloat {
329/// #   fn is_in_range_of<T>(&self) -> bool { todo!() }
330/// #   fn is_integral(&self) -> bool { todo!() }
331/// # }
332///
333/// impl ConvTo<MyFloat, Exact> for i32 {
334///     type Error = Error;
335///
336///     fn try_conv_to(_: Exact, f: MyFloat) -> Result<Self, Self::Error> {
337///         if f.is_in_range_of::<i32>() {
338///             if f.is_integral() {
339///                 Ok(todo!())
340///             } else {
341///                 Err(Error::Inexact)
342///             }
343///         } else {
344///             Err(Error::Range)
345///         }
346///     }
347///
348///     // optionally also impl fn conv
349/// }
350///
351/// impl ConvTo<MyFloat, Approx> for i32 {
352///     type Error = RangeError;
353///
354///     fn try_conv_to(_: Approx, f: MyFloat) -> Result<Self, Self::Error> {
355///         if f.is_in_range_of::<i32>() {
356///             Ok(todo!())
357///         } else {
358///             Err(RangeError)
359///         }
360///     }
361///
362///     // optionally also impl fn conv
363/// }
364///
365/// // optionally also implement ConvTo for other rounding modes
366/// ```
367pub trait ConvTo<S, R: Rounding>: Sized {
368    /// Conversion error type
369    ///
370    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
371    type Error: Into<R::MaximumError> + core::error::Error;
372
373    /// Try converting from `S` to `Self`, rounding according to `mode`
374    fn try_conv_to(mode: R, s: S) -> Result<Self, Self::Error>;
375
376    /// Convert from `S` to `Self`, rounding according to `mode`
377    ///
378    /// Use this method only when success is expected. On error, this method may
379    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
380    ///
381    /// # Implementing
382    ///
383    /// Implementing this method directly (with fallback behaviour) is optional.
384    /// In debug builds, this method must panic on error.
385    fn conv_to(mode: R, s: S) -> Self {
386        Self::try_conv_to(mode, s).unwrap_or_else(|e| panic!("ConvTo::conv_to(_) failed: {e}"))
387    }
388}
389
390/// Generic "into" conversion trait with specified rounding mode
391///
392/// This trait supports [`Into`]- and [`TryInto`]-like conversions, but with a
393/// specified rounding mode:
394/// -   Conversions may be *fallible*, like [`TryInto`].
395/// -   Conversions may be *lossy*, according to the [`Rounding`] mode used.
396/// -   Conversions must be *value-preserving*, like [`Into`]. For example,
397///     `-1_i8` and `-1_i32` are conceptually the same value while `255_u8` is
398///     conceptually a different value.
399///
400/// The [`Rounding`] mode must be specified:
401/// ```
402/// # use easy_cast::{CastTo, Floor, Nearest};
403/// let x: i32 = 3.14192.cast_to(Floor);
404/// assert_eq!(x, 3);
405///
406/// let y = (1i32 << 30) - 1;
407/// let z: f32 = y.cast_to(Nearest);  // this example rounds up
408/// assert_eq!(z as i32, 1i32 << 30);
409/// ```
410/// Usage with [`Exact`] and [`Approx`] is equivalent to usage of [`Cast`] and
411/// [`CastApprox`] respectively.
412///
413/// This trait is automatically implemented for every implementation of [`ConvTo`].
414pub trait CastTo<T, R: Rounding>: Sized {
415    /// Conversion error type
416    ///
417    /// This should be one of [`Infallible`], [`RangeError`] or [`Error`].
418    type Error: Into<R::MaximumError> + core::error::Error;
419
420    /// Try converting from `Self` to `T`, rounding according to `mode`
421    fn try_cast_to(self, mode: R) -> Result<T, Self::Error>;
422
423    /// Convert from `Self` to `T`, rounding according to `mode`
424    ///
425    /// Use this method only when success is expected. On error, this method may
426    /// panic or may exhibit [§ Fallback behaviour](crate#fallback-behaviour).
427    fn cast_to(self, mode: R) -> T;
428}
429
430impl<R: Rounding, S, T: ConvTo<S, R>> CastTo<T, R> for S {
431    type Error = T::Error;
432
433    fn try_cast_to(self, mode: R) -> Result<T, Self::Error> {
434        T::try_conv_to(mode, self)
435    }
436
437    fn cast_to(self, mode: R) -> T {
438        T::conv_to(mode, self)
439    }
440}