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]<S, R></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}