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 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}