diff --git a/src/cast.rs b/src/cast/mod.rs similarity index 99% rename from src/cast.rs rename to src/cast/mod.rs index 20e9340..16c6073 100644 --- a/src/cast.rs +++ b/src/cast/mod.rs @@ -1,3 +1,5 @@ +pub mod safe; + use core::mem::size_of; use core::num::Wrapping; use core::{f32, f64}; diff --git a/src/cast/safe/cast.rs b/src/cast/safe/cast.rs new file mode 100644 index 0000000..7be479e --- /dev/null +++ b/src/cast/safe/cast.rs @@ -0,0 +1,103 @@ +/// Cast from one numeric type to another. +/// +/// The `CastFrom` trait is similar to `std::convert::From`. However, +/// while `std::convert::From` performs a logical conversion, `CastFrom` +/// performs a bitwise cast from one number to another; possibly of a different +/// size, signedness or even numeric type. +/// +/// Unless you really know what you are doing, you probably don't want this +/// trait. Instead, you should check out the following traits: +/// +/// * `GrowFrom` +/// * `TrimFrom` +/// * `SignCast` +pub trait CastFrom { + fn cast(value: T) -> Self; +} + +/// Cast from one numeric type to another. +/// +/// The `CastInto` trait is similar to `std::convert::Into`. However, +/// while `std::convert::Into` performs a logical conversion, `CastInto` +/// performs a bitwise cast from one number to another; possibly of a different +/// size, signedness or even numeric type. +/// +/// Unless you really know what you are doing, you probably don't want this +/// trait. Instead, you should check out the following traits: +/// +/// * `GrowInto` +/// * `TrimInto` +/// * `SignCast` +pub trait CastInto { + fn cast(self) -> T; +} + +// CastFrom implies CastInto +impl CastInto for T where U: CastFrom +{ + #[inline] + #[must_use] + fn cast(self) -> U { + U::cast(self) + } +} + +// CastFrom (and thus CastInto) is reflexive +impl CastFrom for T { + #[inline] + #[must_use] + fn cast(t: T) -> T { t } +} + +macro_rules! cast_impl { + ($from:ty > $into:ty) => ( + impl CastFrom<$from> for $into { + #[inline] + #[must_use] + fn cast(value: $from) -> $into { + value as $into + } + } + ); + + (i128 => $into:ty) => ( + #[cfg(has_i128)] + cast_impl! { i128 > $into } + ); + + (u128 => $into:ty) => ( + #[cfg(has_i128)] + cast_impl! { u128 > $into } + ); + + ($from:ty => i128) => ( + #[cfg(has_i128)] + cast_impl! { $from > i128 } + ); + + ($from:ty => u128) => ( + #[cfg(has_i128)] + cast_impl! { $from > u128 } + ); + + ($from:ty => $into:ty) => ( + cast_impl! { $from > $into } + ); + + ($kind:ty, $($next:ty),+) => ( + $( + cast_impl! { $kind => $next } + cast_impl! { $next => $kind } + )+ + + cast_impl! { $($next),+ } + ); + + ($kind:ty) => (); +} + +cast_impl! { + usize, u128, u64, u32, u16, u8, + isize, i128, i64, i32, i16, i8, + f32, f64 +} diff --git a/src/cast/safe/grow.rs b/src/cast/safe/grow.rs new file mode 100644 index 0000000..ac9f8b9 --- /dev/null +++ b/src/cast/safe/grow.rs @@ -0,0 +1,113 @@ +/// Cast from a smaller number to a larger one without changing sign. +/// +/// The `GrowFrom` trait is similar to `std::convert::From`. However, +/// while `std::convert::From` performs a logical conversion, `GrowFrom` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly larger size. `GrowFrom` will **never** decrease the size +/// of a number or change from an integer of one signedness to the other. +pub trait GrowFrom { + #[inline] + #[must_use] + fn grow(value: T) -> Self; +} + +/// Cast from a smaller number to a larger one without changing sign. +/// +/// The `GrowInto` trait is similar to `std::convert::Into`. However, +/// while `std::convert::Into` performs a logical conversion, `GrowInto` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly larger size. `GrowInto` will **never** decrease the size +/// of a number or change from an integer of one signedness to the other. +pub trait GrowInto { + #[inline] + #[must_use] + fn grow(self) -> T; +} + +// GrowFrom implies GrowInto +impl GrowInto for T where U: GrowFrom +{ + #[inline] + #[must_use] + fn grow(self) -> U { + U::grow(self) + } +} + +// GrowFrom (and thus GrowInto) is reflexive +impl GrowFrom for T { + #[inline] + #[must_use] + fn grow(t: T) -> T { t } +} + +macro_rules! grow_impl { + ($from:ty > $into:ty) => ( + impl GrowFrom<$from> for $into { + #[inline] + #[must_use] + fn grow(value: $from) -> $into { + value as $into + } + } + ); + + (i128 => $into:ty) => ( + #[cfg(has_i128)] + grow_impl! { i128 > $into } + ); + + (u128 => $into:ty) => ( + #[cfg(has_i128)] + grow_impl! { u128 > $into } + ); + + ($from:ty => i128) => ( + #[cfg(has_i128)] + grow_impl! { $from > i128 } + ); + + ($from:ty => u128) => ( + #[cfg(has_i128)] + grow_impl! { $from > u128 } + ); + + ($from:ty => $into:ty) => ( + grow_impl! { $from > $into } + ); + + ($($from:ty : $($into:ty),+)+) => ( + $( $( grow_impl! { $from => $into } )+ )+ + ); +} + +#[cfg(target_pointer_width = "64")] +grow_impl! { + u64: usize + i64: isize +} + +#[cfg(target_pointer_width = "32")] +grow_impl! { + usize: u32 + isize: i32 +} + +grow_impl! { + usize: u128, u64 + isize: i128, i64 + + u64: u128 + i64: i128 + + u32: usize, u128, u64 + i32: isize, i128, i64 + + u16: usize, u128, u64, u32 + i16: isize, i128, i64, i32 + + u8: usize, u128, u64, u32, u16 + i8: isize, i128, i64, i32, i16 + + f32: f64 +} diff --git a/src/cast/safe/mod.rs b/src/cast/safe/mod.rs new file mode 100644 index 0000000..ea55efe --- /dev/null +++ b/src/cast/safe/mod.rs @@ -0,0 +1,56 @@ +//! Traits for safe casting in a generic context. +//! +//! Each of the traits herein has a varying degree of safety. Raw casting is +//! implicitly dangerous because it can perform four possible actions: +//! * Convert from integer to float or vice versa. +//! * Convert from signed to unsigned or vice versa. +//! * Increases the size (or precision) of a number without loss. +//! * Decreases the size (or precision) of a number with loss. +//! +//! By using the raw cast operation you may get unintentional side effects. +//! These will often compile without warning and appear only as bugs later. +//! +//! The purpose of this module is to provide zero-cost abstraction traits that +//! encapsulate each discrete action of a cast. When using the methods on these +//! traits, you will only ever get the intended explicit behavior. +//! +//! For example, here is a generic implementation of a trivial function which +//! takes any integer type that can hold at least 12 bits and returns the 12 +//! least significant bits. Note that attempting to pass an 8-bit integer to +//! this function would result in a compiler error (since `TrimInto` is +//! not implemented for `u8`). +//! +//! ```rust +//! use num_traits::cast::safe::{SignCast, TrimInto}; +//! +//! const MASK: u16 = 0b0000111111111111; +//! +//! fn low12, C: SignCast>(x: C) -> u16 { +//! x.unsigned().trim() & MASK +//! } +//! +//! assert_eq!(low12(u64::max_value()), u64::max_value() as u16 & MASK); +//! assert_eq!(low12(-1i32), -1i32 as u16 & MASK); +//! ``` +//! +//! It is recommended that you use the most specific type of cast appropriate +//! for your own use. Using a more generalized cast results in reduced compile +//! time validation of your code. +//! +//! Recommended cast types are: +//! +//! * `GrowFrom` / `GrowInto` +//! * `TrimFrom` / `TrimInto` +//! * `SignCast` + +mod cast; +mod grow; +mod sign; +mod trim; +mod size; + +pub use self::cast::{CastFrom, CastInto}; +pub use self::size::{SizeFrom, SizeInto}; +pub use self::grow::{GrowFrom, GrowInto}; +pub use self::trim::{TrimFrom, TrimInto}; +pub use self::sign::SignCast; diff --git a/src/cast/safe/sign.rs b/src/cast/safe/sign.rs new file mode 100644 index 0000000..360f0e7 --- /dev/null +++ b/src/cast/safe/sign.rs @@ -0,0 +1,232 @@ +/// Casts an integer to a different sign without changing its size. +/// +/// `SignCast` exposes the cast operator in generic contexts. It is used to +/// perform a signed cast between integers of the same size. The `SignCast` +/// trait will **never** change the size of the integer. +pub trait SignCast { + type Unsigned; + type Signed; + + fn unsigned(self) -> Self::Unsigned; + fn signed(self) -> Self::Signed; +} + +macro_rules! sign_impl { + ( $( $usig:ty : $sign:ty )+ ) => ( + $( + impl SignCast for $usig { + type Unsigned = $usig; + type Signed = $sign; + + #[inline] + #[must_use] + fn unsigned(self) -> $usig { + self + } + + #[inline] + #[must_use] + fn signed(self) -> $sign { + self as $sign + } + } + + impl SignCast for $sign { + type Unsigned = $usig; + type Signed = $sign; + + #[inline] + #[must_use] + fn unsigned(self) -> $usig { + self as $usig + } + + #[inline] + #[must_use] + fn signed(self) -> $sign { + self + } + } + )+ + ) +} + +#[cfg(has_i128)] +sign_impl! { + u128 : i128 +} + +sign_impl! { + usize : isize + u64 : i64 + u32 : i32 + u16 : i16 + u8 : i8 +} + +#[cfg(test)] +mod tests { + use super::SignCast; + + #[test] + fn test_size() { + // Signed to unsigned cast + assert_eq!(isize::min_value().unsigned(), isize::min_value() as usize); + assert_eq!(0isize.unsigned(), 0usize); + assert_eq!(isize::max_value().unsigned(), isize::max_value() as usize); + + // Signed to signed cast + assert_eq!(isize::min_value().signed(), isize::min_value()); + assert_eq!(0isize.signed(), 0isize); + assert_eq!(isize::max_value().signed(), isize::max_value()); + + // Unsigned to signed cast + assert_eq!(usize::min_value().signed(), usize::min_value() as isize); + assert_eq!(usize::max_value().signed(), usize::max_value() as isize); + + // Unsigned to unsigned cast + assert_eq!(usize::min_value().unsigned(), usize::min_value()); + assert_eq!(usize::max_value().unsigned(), usize::max_value()); + + // Test reciprocity + assert_eq!(isize::min_value().unsigned().signed(), isize::min_value()); + assert_eq!(isize::max_value().unsigned().signed(), isize::max_value()); + assert_eq!(usize::min_value().signed().unsigned(), usize::min_value()); + assert_eq!(usize::max_value().signed().unsigned(), usize::max_value()); + } + + #[test] + fn test_128() { + // Signed to unsigned cast + assert_eq!(i128::min_value().unsigned(), i128::min_value() as u128); + assert_eq!(0i128.unsigned(), 0u128); + assert_eq!(i128::max_value().unsigned(), i128::max_value() as u128); + + // Signed to signed cast + assert_eq!(i128::min_value().signed(), i128::min_value()); + assert_eq!(0i128.signed(), 0i128); + assert_eq!(i128::max_value().signed(), i128::max_value()); + + // Unsigned to signed cast + assert_eq!(u128::min_value().signed(), u128::min_value() as i128); + assert_eq!(u128::max_value().signed(), u128::max_value() as i128); + + // Unsigned to unsigned cast + assert_eq!(u128::min_value().unsigned(), u128::min_value()); + assert_eq!(u128::max_value().unsigned(), u128::max_value()); + + // Test reciprocity + assert_eq!(i128::min_value().unsigned().signed(), i128::min_value()); + assert_eq!(i128::max_value().unsigned().signed(), i128::max_value()); + assert_eq!(u128::min_value().signed().unsigned(), u128::min_value()); + assert_eq!(u128::max_value().signed().unsigned(), u128::max_value()); + } + + #[test] + fn test_64() { + // Signed to unsigned cast + assert_eq!(i64::min_value().unsigned(), i64::min_value() as u64); + assert_eq!(0i64.unsigned(), 0u64); + assert_eq!(i64::max_value().unsigned(), i64::max_value() as u64); + + // Signed to signed cast + assert_eq!(i64::min_value().signed(), i64::min_value()); + assert_eq!(0i64.signed(), 0i64); + assert_eq!(i64::max_value().signed(), i64::max_value()); + + // Unsigned to signed cast + assert_eq!(u64::min_value().signed(), u64::min_value() as i64); + assert_eq!(u64::max_value().signed(), u64::max_value() as i64); + + // Unsigned to unsigned cast + assert_eq!(u64::min_value().unsigned(), u64::min_value()); + assert_eq!(u64::max_value().unsigned(), u64::max_value()); + + // Test reciprocity + assert_eq!(i64::min_value().unsigned().signed(), i64::min_value()); + assert_eq!(i64::max_value().unsigned().signed(), i64::max_value()); + assert_eq!(u64::min_value().signed().unsigned(), u64::min_value()); + assert_eq!(u64::max_value().signed().unsigned(), u64::max_value()); + } + + #[test] + fn test_32() { + // Signed to unsigned cast + assert_eq!(i32::min_value().unsigned(), i32::min_value() as u32); + assert_eq!(0i32.unsigned(), 0u32); + assert_eq!(i32::max_value().unsigned(), i32::max_value() as u32); + + // Signed to signed cast + assert_eq!(i32::min_value().signed(), i32::min_value()); + assert_eq!(0i32.signed(), 0i32); + assert_eq!(i32::max_value().signed(), i32::max_value()); + + // Unsigned to signed cast + assert_eq!(u32::min_value().signed(), u32::min_value() as i32); + assert_eq!(u32::max_value().signed(), u32::max_value() as i32); + + // Unsigned to unsigned cast + assert_eq!(u32::min_value().unsigned(), u32::min_value()); + assert_eq!(u32::max_value().unsigned(), u32::max_value()); + + // Test reciprocity + assert_eq!(i32::min_value().unsigned().signed(), i32::min_value()); + assert_eq!(i32::max_value().unsigned().signed(), i32::max_value()); + assert_eq!(u32::min_value().signed().unsigned(), u32::min_value()); + assert_eq!(u32::max_value().signed().unsigned(), u32::max_value()); + } + + #[test] + fn test_16() { + // Signed to unsigned cast + assert_eq!(i16::min_value().unsigned(), i16::min_value() as u16); + assert_eq!(0i16.unsigned(), 0u16); + assert_eq!(i16::max_value().unsigned(), i16::max_value() as u16); + + // Signed to signed cast + assert_eq!(i16::min_value().signed(), i16::min_value()); + assert_eq!(0i16.signed(), 0i16); + assert_eq!(i16::max_value().signed(), i16::max_value()); + + // Unsigned to signed cast + assert_eq!(u16::min_value().signed(), u16::min_value() as i16); + assert_eq!(u16::max_value().signed(), u16::max_value() as i16); + + // Unsigned to unsigned cast + assert_eq!(u16::min_value().unsigned(), u16::min_value()); + assert_eq!(u16::max_value().unsigned(), u16::max_value()); + + // Test reciprocity + assert_eq!(i16::min_value().unsigned().signed(), i16::min_value()); + assert_eq!(i16::max_value().unsigned().signed(), i16::max_value()); + assert_eq!(u16::min_value().signed().unsigned(), u16::min_value()); + assert_eq!(u16::max_value().signed().unsigned(), u16::max_value()); + } + + #[test] + fn test_8() { + // Signed to unsigned cast + assert_eq!(i8::min_value().unsigned(), i8::min_value() as u8); + assert_eq!(0i8.unsigned(), 0u8); + assert_eq!(i8::max_value().unsigned(), i8::max_value() as u8); + + // Signed to signed cast + assert_eq!(i8::min_value().signed(), i8::min_value()); + assert_eq!(0i8.signed(), 0i8); + assert_eq!(i8::max_value().signed(), i8::max_value()); + + // Unsigned to signed cast + assert_eq!(u8::min_value().signed(), u8::min_value() as i8); + assert_eq!(u8::max_value().signed(), u8::max_value() as i8); + + // Unsigned to unsigned cast + assert_eq!(u8::min_value().unsigned(), u8::min_value()); + assert_eq!(u8::max_value().unsigned(), u8::max_value()); + + // Test reciprocity + assert_eq!(i8::min_value().unsigned().signed(), i8::min_value()); + assert_eq!(i8::max_value().unsigned().signed(), i8::max_value()); + assert_eq!(u8::min_value().signed().unsigned(), u8::min_value()); + assert_eq!(u8::max_value().signed().unsigned(), u8::max_value()); + } +} diff --git a/src/cast/safe/size.rs b/src/cast/safe/size.rs new file mode 100644 index 0000000..d085fc4 --- /dev/null +++ b/src/cast/safe/size.rs @@ -0,0 +1,103 @@ +/// Cast between different sized numbers without changing sign. +/// +/// The `SizeFrom` trait is similar to `std::convert::From`. However, +/// while `std::convert::From` performs a logical conversion, `SizeFrom` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly different size. `TrimFrom` will **never** change from an +/// integer of one signedness to the other. +/// +/// Unless you really know what you are doing, you probably don't want this +/// trait. Instead, you should check out the following traits: +/// +/// * `GrowFrom` +/// * `TrimFrom` +/// * `SignCast` +pub trait SizeFrom { + fn size(value: T) -> Self; +} + +/// Cast between different sized numbers without changing sign. +/// +/// The `SizeInto` trait is similar to `std::convert::Into`. However, +/// while `std::convert::Into` performs a logical conversion, `SizeInto` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly different size. `TrimInto` will **never** change from an +/// integer of one signedness to the other. +/// +/// Unless you really know what you are doing, you probably don't want this +/// trait. Instead, you should check out the following traits: +/// +/// * `GrowInto` +/// * `TrimInto` +/// * `SignCast` +pub trait SizeInto { + fn size(self) -> T; +} + +// SizeFrom implies SizeInto +impl SizeInto for T where U: SizeFrom +{ + #[inline] + #[must_use] + fn size(self) -> U { + U::size(self) + } +} + +// SizeFrom (and thus SizeInto) is reflexive +impl SizeFrom for T { + #[inline] + #[must_use] + fn size(t: T) -> T { t } +} + +macro_rules! size_impl { + ($from:ty > $into:ty) => ( + impl SizeFrom<$from> for $into { + #[inline] + #[must_use] + fn size(value: $from) -> $into { + value as $into + } + } + ); + + (i128 => $into:ty) => ( + #[cfg(has_i128)] + size_impl! { i128 > $into } + ); + + (u128 => $into:ty) => ( + #[cfg(has_i128)] + size_impl! { u128 > $into } + ); + + ($from:ty => i128) => ( + #[cfg(has_i128)] + size_impl! { $from > i128 } + ); + + ($from:ty => u128) => ( + #[cfg(has_i128)] + size_impl! { $from > u128 } + ); + + ($from:ty => $into:ty) => ( + size_impl! { $from > $into } + ); + + ($kind:ty, $($next:ty),+) => ( + $( + size_impl! { $kind => $next } + size_impl! { $next => $kind } + )+ + + size_impl! { $($next),+ } + ); + + ($kind:ty) => (); +} + +size_impl! { usize, u128, u64, u32, u16, u8 } +size_impl! { isize, i128, i64, i32, i16, i8 } +size_impl! { f32, f64 } diff --git a/src/cast/safe/trim.rs b/src/cast/safe/trim.rs new file mode 100644 index 0000000..2fab6fe --- /dev/null +++ b/src/cast/safe/trim.rs @@ -0,0 +1,109 @@ +/// Cast from a larger number to a smaller one without changing sign. +/// +/// The `TrimFrom` trait is similar to `std::convert::From`. However, +/// while `std::convert::From` performs a logical conversion, `TrimFrom` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly smaller size. `TrimFrom` will **never** increase the size +/// of a number or change from an integer of one signedness to the other. +pub trait TrimFrom { + fn trim(value: T) -> Self; +} + +/// Cast from a larger number to a smaller one without changing sign. +/// +/// The `TrimInto` trait is similar to `std::convert::Into`. However, +/// while `std::convert::Into` performs a logical conversion, `TrimInto` +/// performs a bitwise cast from a number to a number of the same sign but +/// of a possibly smaller size. `TrimInto` will **never** increase the size +/// of a number or change from an integer of one signedness to the other. +pub trait TrimInto { + fn trim(self) -> T; +} + +// TrimFrom implies TrimInto +impl TrimInto for T where U: TrimFrom +{ + #[inline] + #[must_use] + fn trim(self) -> U { + U::trim(self) + } +} + +// TrimFrom (and thus TrimInto) is reflexive +impl TrimFrom for T { + #[inline] + #[must_use] + fn trim(t: T) -> T { t } +} + +macro_rules! trim_impl { + ($from:ty > $into:ty) => ( + impl TrimFrom<$from> for $into { + #[inline] + #[must_use] + fn trim(value: $from) -> $into { + value as $into + } + } + ); + + (i128 => $into:ty) => ( + #[cfg(has_i128)] + trim_impl! { i128 > $into } + ); + + (u128 => $into:ty) => ( + #[cfg(has_i128)] + trim_impl! { u128 > $into } + ); + + ($from:ty => i128) => ( + #[cfg(has_i128)] + trim_impl! { $from > i128 } + ); + + ($from:ty => u128) => ( + #[cfg(has_i128)] + trim_impl! { $from > u128 } + ); + + ($from:ty => $into:ty) => ( + trim_impl! { $from > $into } + ); + + ($($from:ty : $($into:ty),+)+) => ( + $( $( trim_impl! { $from => $into } )+ )+ + ); +} + +#[cfg(target_pointer_width = "64")] +trim_impl! { + usize: u64 + isize: i64 +} + +#[cfg(target_pointer_width = "32")] +trim_impl! { + u32: usize + i32: isize +} + +trim_impl! { + usize: u32, u16, u8 + isize: i32, i16, i8 + + u128: usize, u64, u32, u16, u8 + i128: isize, i64, i32, i16, i8 + + u64: usize, u32, u16, u8 + i64: isize, i32, i16, i8 + + u32: u16, u8 + i32: i16, i8 + + u16: u8 + i16: i8 + + f64: f32 +}