Skip to main content

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