easy_cast/lib.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//! # Converting values
7//!
8//! This library exists to make numeric type conversions **easy** and
9//! **generic** without resorting to the `as` keyword.
10//!
11//! - Use [`Cast`] and [`Conv`] instead of [`Into`] and [`From`] for exact
12//! conversions
13//! - Use [`CastApprox`] and [`ConvApprox`] for approximate conversions
14//! (rounding mode is implementation-defined just like `as`)
15//! - Use [`CastTo`] and [`ConvTo`] for conversions with a specific rounding
16//! mode (see [§ Rounding modes](#rounding-modes)).
17//!
18//! If this sounds like a lot of traits, consider the above are all essentially
19//! syntactic sugar for [`ConvTo`] (see
20//! [§ Implementing traits](#implementing-traits)).
21//!
22//! ### Quick example
23//!
24//! ```
25//! use easy_cast::{Cast, Conv, CastApprox, CastTo, Nearest};
26//! let _: i32 = 15_usize.cast(); // exact conversion
27//! let _ = usize::conv(20_u32); // exact conversion
28//! let _: f32 = u32::MAX.cast_approx(); // approximates to 2^32
29//! let _: i32 = 11.9_f32.cast_to(Nearest); // rounds to 12
30//! ```
31//!
32//! ## Rounding modes
33//!
34//! The [`Rounding`] trait (used with [`CastTo`] and [`ConvTo`]) supports
35//! genericity over rounding modes:
36//!
37//! - [`Exact`] specifies that no rounding is allowed (loss of precision is an
38//! error)
39//! - [`Approx`] specifies that rounding is allowed. The rounding mode used is
40//! a property of the implementation, but usually aligns with
41//! [`as` numeric casts].
42//! - [`Trunc`], [`Floor`], [`Ceil`] and [`Nearest`] allow more precise
43//! control over rounding
44//!
45//! All rounding modes require that the result is close to the input value. For
46//! a more precise definition, see
47//! [`§ Limits of approximation`](Approx#limits-of-approximation).
48//!
49//! ## Error handling
50//!
51//! Unlike [`From`] or [`TryFrom`], this library's traits are implemented
52//! regardless of fallibility. All conversion traits have an associated `Error`
53//! type which is expected to be one of:
54//!
55//! - [`std::convert::Infallible`] for infallible conversions
56//! - [`RangeError`] for conversions which may fail due to domain errors
57//! - [`Error`] for conversions which may fail due to domain or
58//! loss-of-precision errors.
59//!
60//! Further, all traits have two methods:
61//!
62//! - A `try_` method (e.g. [`Cast::try_cast`]) which returns a [`Result`]
63//! - A "derived" method (e.g. [`Cast::cast`]) with
64//! [§ Fallback behaviour](#fallback-behaviour)
65//!
66//! ### Fallback behaviour
67//!
68//! In debug builds, the "derived" method must panic on failure. This is also
69//! the case if the `always_assert` feature flag is enabled (for this library's
70//! implementations).
71//!
72//! Otherwise (in release builds without extra assertions enabled), more
73//! flexible behaviour of the "derived" methods is allowed. The implementations
74//! provided by `easy-cast` mostly reduce to [`as` numeric casts] (with extra
75//! rounding where required).
76//!
77//! ## Implementing traits
78//!
79//! Implement conversions which cannot lose precision using [`ConvExact`].
80//! Implement all other conversions using [`ConvTo`] for one or several
81//! [`Rounding`] modes.
82//!
83//! [`TryFrom`]: core::convert::TryFrom
84//! [`TryInto`]: core::convert::TryInto
85//! [`as` numeric casts]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.as.numeric
86
87#![deny(missing_docs)]
88#![cfg_attr(not(feature = "std"), no_std)]
89#![cfg_attr(docsrs, feature(doc_cfg))]
90
91mod impl_basic;
92mod impl_float;
93mod impl_int;
94mod impl_num;
95mod impl_ops;
96mod impl_range;
97mod rounding;
98
99pub mod traits;
100
101#[doc(inline)]
102pub use rounding::*;
103#[doc(inline)]
104pub use traits::*;
105
106use core::convert::Infallible;
107
108/// Source value lies outside of target type's range
109///
110/// This error indicates that the input value is outside the range (domain) of
111/// the target type. This error type is used for both conversions where
112/// loss-of-precision is impossible and those where rounding is intended.
113#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
114pub struct RangeError;
115
116impl From<Infallible> for RangeError {
117 #[inline]
118 fn from(error: Infallible) -> Self {
119 match error {}
120 }
121}
122
123impl core::fmt::Display for RangeError {
124 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
125 write!(f, "source value not in target range")
126 }
127}
128
129impl core::error::Error for RangeError {}
130
131/// Error types for conversions
132#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
133pub enum Error {
134 /// Source value lies outside of target type's range
135 ///
136 /// This error indicates that the input value is outside the range (domain)
137 /// of the target type.
138 /// More precisely, all values of the target type's domain are either
139 /// incomparable to the source value or are closer to another value within
140 /// the target type's domain than to the source value.
141 ///
142 /// As typical example, attempting to convert `-1_i8` to `u8` results in a
143 /// `Range` error. A special case is [`f32::NAN`] which, being (literally)
144 /// "Not a Number" is outside the domain of the target type and thus a
145 /// `Range` error (even where the target type has its own `NAN` value).
146 Range,
147 /// Loss of precision
148 ///
149 /// This error indicates that, though the input value is inside the range
150 /// (domain) of the target type, conversion without loss of precision is
151 /// impossible.
152 ///
153 /// For example, attempting to convert `2.1_f32` to `i32` without rounding
154 /// results in an `Inexact` error.
155 Inexact,
156}
157
158impl From<Infallible> for Error {
159 #[inline]
160 fn from(error: Infallible) -> Self {
161 match error {}
162 }
163}
164
165impl From<RangeError> for Error {
166 #[inline]
167 fn from(_: RangeError) -> Self {
168 Self::Range
169 }
170}
171
172impl core::fmt::Display for Error {
173 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174 match self {
175 Error::Range => write!(f, "source value not in target range"),
176 Error::Inexact => write!(f, "loss of precision"),
177 }
178 }
179}
180
181impl core::error::Error for Error {}