Add safe cast operation traits

This commit adds safe cast operation traits which each have only one of
the properties from a naked cast. This allows the clear definition of
specific properties in a generic context.

All methods defined in this commit are zero-cost abstractions.
This commit is contained in:
Nathaniel McCallum 2018-09-29 13:50:27 -04:00
parent 5c24fcc4a7
commit 955c45b933
7 changed files with 718 additions and 0 deletions

View File

@ -1,3 +1,5 @@
pub mod safe;
use core::mem::size_of;
use core::num::Wrapping;
use core::{f32, f64};

103
src/cast/safe/cast.rs Normal file
View File

@ -0,0 +1,103 @@
/// Cast from one numeric type to another.
///
/// The `CastFrom<T>` trait is similar to `std::convert::From<T>`. However,
/// while `std::convert::From<T>` performs a logical conversion, `CastFrom<T>`
/// 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<T>`
/// * `TrimFrom<T>`
/// * `SignCast`
pub trait CastFrom<T> {
fn cast(value: T) -> Self;
}
/// Cast from one numeric type to another.
///
/// The `CastInto<T>` trait is similar to `std::convert::Into<T>`. However,
/// while `std::convert::Into<T>` performs a logical conversion, `CastInto<T>`
/// 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<T>`
/// * `TrimInto<T>`
/// * `SignCast`
pub trait CastInto<T> {
fn cast(self) -> T;
}
// CastFrom implies CastInto
impl<T, U> CastInto<U> for T where U: CastFrom<T>
{
#[inline]
#[must_use]
fn cast(self) -> U {
U::cast(self)
}
}
// CastFrom (and thus CastInto) is reflexive
impl<T> CastFrom<T> 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
}

113
src/cast/safe/grow.rs Normal file
View File

@ -0,0 +1,113 @@
/// Cast from a smaller number to a larger one without changing sign.
///
/// The `GrowFrom<T>` trait is similar to `std::convert::From<T>`. However,
/// while `std::convert::From<T>` performs a logical conversion, `GrowFrom<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly larger size. `GrowFrom<T>` will **never** decrease the size
/// of a number or change from an integer of one signedness to the other.
pub trait GrowFrom<T> {
#[inline]
#[must_use]
fn grow(value: T) -> Self;
}
/// Cast from a smaller number to a larger one without changing sign.
///
/// The `GrowInto<T>` trait is similar to `std::convert::Into<T>`. However,
/// while `std::convert::Into<T>` performs a logical conversion, `GrowInto<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly larger size. `GrowInto<T>` will **never** decrease the size
/// of a number or change from an integer of one signedness to the other.
pub trait GrowInto<T> {
#[inline]
#[must_use]
fn grow(self) -> T;
}
// GrowFrom implies GrowInto
impl<T, U> GrowInto<U> for T where U: GrowFrom<T>
{
#[inline]
#[must_use]
fn grow(self) -> U {
U::grow(self)
}
}
// GrowFrom (and thus GrowInto) is reflexive
impl<T> GrowFrom<T> 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
}

56
src/cast/safe/mod.rs Normal file
View File

@ -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<u16>` is
//! not implemented for `u8`).
//!
//! ```rust
//! use num_traits::cast::safe::{SignCast, TrimInto};
//!
//! const MASK: u16 = 0b0000111111111111;
//!
//! fn low12<T: TrimInto<u16>, C: SignCast<Unsigned=T>>(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<T>` / `GrowInto<T>`
//! * `TrimFrom<T>` / `TrimInto<T>`
//! * `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;

232
src/cast/safe/sign.rs Normal file
View File

@ -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());
}
}

103
src/cast/safe/size.rs Normal file
View File

@ -0,0 +1,103 @@
/// Cast between different sized numbers without changing sign.
///
/// The `SizeFrom<T>` trait is similar to `std::convert::From<T>`. However,
/// while `std::convert::From<T>` performs a logical conversion, `SizeFrom<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly different size. `TrimFrom<T>` 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<T>`
/// * `TrimFrom<T>`
/// * `SignCast`
pub trait SizeFrom<T> {
fn size(value: T) -> Self;
}
/// Cast between different sized numbers without changing sign.
///
/// The `SizeInto<T>` trait is similar to `std::convert::Into<T>`. However,
/// while `std::convert::Into<T>` performs a logical conversion, `SizeInto<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly different size. `TrimInto<T>` 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<T>`
/// * `TrimInto<T>`
/// * `SignCast`
pub trait SizeInto<T> {
fn size(self) -> T;
}
// SizeFrom implies SizeInto
impl<T, U> SizeInto<U> for T where U: SizeFrom<T>
{
#[inline]
#[must_use]
fn size(self) -> U {
U::size(self)
}
}
// SizeFrom (and thus SizeInto) is reflexive
impl<T> SizeFrom<T> 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 }

109
src/cast/safe/trim.rs Normal file
View File

@ -0,0 +1,109 @@
/// Cast from a larger number to a smaller one without changing sign.
///
/// The `TrimFrom<T>` trait is similar to `std::convert::From<T>`. However,
/// while `std::convert::From<T>` performs a logical conversion, `TrimFrom<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly smaller size. `TrimFrom<T>` will **never** increase the size
/// of a number or change from an integer of one signedness to the other.
pub trait TrimFrom<T> {
fn trim(value: T) -> Self;
}
/// Cast from a larger number to a smaller one without changing sign.
///
/// The `TrimInto<T>` trait is similar to `std::convert::Into<T>`. However,
/// while `std::convert::Into<T>` performs a logical conversion, `TrimInto<T>`
/// performs a bitwise cast from a number to a number of the same sign but
/// of a possibly smaller size. `TrimInto<T>` will **never** increase the size
/// of a number or change from an integer of one signedness to the other.
pub trait TrimInto<T> {
fn trim(self) -> T;
}
// TrimFrom implies TrimInto
impl<T, U> TrimInto<U> for T where U: TrimFrom<T>
{
#[inline]
#[must_use]
fn trim(self) -> U {
U::trim(self)
}
}
// TrimFrom (and thus TrimInto) is reflexive
impl<T> TrimFrom<T> 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
}