Skip to content

Commit aa7c15e

Browse files
committed
Merge #17 #21
17: Add AsPrimitive trait for generic casting with `as` r=cuviper a=Enet4 This is my personal attempt at #7. It is fairly similar to what can be found in `asprim`, although implemented from scratch. Please let me know of what you think. Could it use more tests? Should I also leave a safety notice that some conversions with `as` are currently UB (rust-lang/rust#10184)? 21: Add checked shifts r=cuviper a=fabianschuiki Add traits `CheckedShl` and `CheckedShr` that correspond to the standard library's `checked_shl` and `checked_shr` functions. Implement the trait on all primitive integer types by default, akin to what the standard library does. The stdlib is somewhat inconsistent when it comes to the type of the shift amount. The `checked_*` functions have a `u32` shift amount, but the `std::ops::{Shl,Shr}` traits are generic over the shift amount. Also the stdlib implements these traits for all primitive integer types as right-hand sides. Our implementation mimics this behaviour.
3 parents 9c8676e + 31218ad + 809ccff commit aa7c15e

File tree

2 files changed

+151
-1
lines changed

2 files changed

+151
-1
lines changed

src/cast.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,74 @@ impl<T: NumCast> NumCast for Wrapping<T> {
452452
}
453453
}
454454

455+
/// A generic interface for casting between machine scalars with the
456+
/// `as` operator, which admits narrowing and precision loss.
457+
/// Implementers of this trait AsPrimitive should behave like a primitive
458+
/// numeric type (e.g. a newtype around another primitive), and the
459+
/// intended conversion must never fail.
460+
///
461+
/// # Examples
462+
///
463+
/// ```
464+
/// # use num_traits::AsPrimitive;
465+
/// let three: i32 = (3.14159265f32).as_();
466+
/// assert_eq!(three, 3);
467+
/// ```
468+
///
469+
/// # Safety
470+
///
471+
/// Currently, some uses of the `as` operator are not entirely safe.
472+
/// In particular, it is undefined behavior if:
473+
///
474+
/// - A truncated floating point value cannot fit in the target integer
475+
/// type ([#10184](https://github.com/rust-lang/rust/issues/10184));
476+
///
477+
/// ```ignore
478+
/// # use num_traits::AsPrimitive;
479+
/// let x: u8 = (1.04E+17).as_(); // UB
480+
/// ```
481+
///
482+
/// - Or a floating point value does not fit in another floating
483+
/// point type ([#15536](https://github.com/rust-lang/rust/issues/15536)).
484+
///
485+
/// ```ignore
486+
/// # use num_traits::AsPrimitive;
487+
/// let x: f32 = (1e300f64).as_(); // UB
488+
/// ```
489+
///
490+
pub trait AsPrimitive<T>: 'static + Copy
491+
where
492+
T: 'static + Copy
493+
{
494+
/// Convert a value to another, using the `as` operator.
495+
fn as_(self) -> T;
496+
}
497+
498+
macro_rules! impl_as_primitive {
499+
($T: ty => $( $U: ty ),* ) => {
500+
$(
501+
impl AsPrimitive<$U> for $T {
502+
#[inline] fn as_(self) -> $U { self as $U }
503+
}
504+
)*
505+
};
506+
}
507+
508+
impl_as_primitive!(u8 => char, u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
509+
impl_as_primitive!(i8 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
510+
impl_as_primitive!(u16 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
511+
impl_as_primitive!(i16 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
512+
impl_as_primitive!(u32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
513+
impl_as_primitive!(i32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
514+
impl_as_primitive!(u64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
515+
impl_as_primitive!(i64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
516+
impl_as_primitive!(usize => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
517+
impl_as_primitive!(isize => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
518+
impl_as_primitive!(f32 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
519+
impl_as_primitive!(f64 => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64, f32, f64);
520+
impl_as_primitive!(char => char, u8, i8, u16, i16, u32, i32, u64, isize, usize, i64);
521+
impl_as_primitive!(bool => u8, i8, u16, i16, u32, i32, u64, isize, usize, i64);
522+
455523
#[test]
456524
fn to_primitive_float() {
457525
use std::f32;
@@ -509,3 +577,15 @@ fn wrapping_is_numcast() {
509577
fn require_numcast<T: NumCast>(_: &T) {}
510578
require_numcast(&Wrapping(42));
511579
}
580+
581+
#[test]
582+
fn as_primitive() {
583+
let x: f32 = (1.625f64).as_();
584+
assert_eq!(x, 1.625f32);
585+
586+
let x: f32 = (3.14159265358979323846f64).as_();
587+
assert_eq!(x, 3.1415927f32);
588+
589+
let x: u8 = (768i16).as_();
590+
assert_eq!(x, 0);
591+
}

src/ops/checked.rs

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::ops::{Add, Sub, Mul, Div};
1+
use std::ops::{Add, Sub, Mul, Div, Shl, Shr};
22

33
/// Performs addition that returns `None` instead of wrapping around on
44
/// overflow.
@@ -90,3 +90,73 @@ checked_impl!(CheckedDiv, checked_div, i32);
9090
checked_impl!(CheckedDiv, checked_div, i64);
9191
checked_impl!(CheckedDiv, checked_div, isize);
9292

93+
/// Performs a left shift that returns `None` on overflow.
94+
pub trait CheckedShl: Sized + Shl<u32, Output=Self> {
95+
/// Shifts a number to the left, checking for overflow. If overflow happens,
96+
/// `None` is returned.
97+
///
98+
/// ```
99+
/// use num_traits::CheckedShl;
100+
///
101+
/// let x: u16 = 0x0001;
102+
///
103+
/// assert_eq!(CheckedShl::checked_shl(&x, 0), Some(0x0001));
104+
/// assert_eq!(CheckedShl::checked_shl(&x, 1), Some(0x0002));
105+
/// assert_eq!(CheckedShl::checked_shl(&x, 15), Some(0x8000));
106+
/// assert_eq!(CheckedShl::checked_shl(&x, 16), None);
107+
/// ```
108+
fn checked_shl(&self, rhs: u32) -> Option<Self>;
109+
}
110+
111+
macro_rules! checked_shift_impl {
112+
($trait_name:ident, $method:ident, $t:ty) => {
113+
impl $trait_name for $t {
114+
#[inline]
115+
fn $method(&self, rhs: u32) -> Option<$t> {
116+
<$t>::$method(*self, rhs)
117+
}
118+
}
119+
}
120+
}
121+
122+
checked_shift_impl!(CheckedShl, checked_shl, u8);
123+
checked_shift_impl!(CheckedShl, checked_shl, u16);
124+
checked_shift_impl!(CheckedShl, checked_shl, u32);
125+
checked_shift_impl!(CheckedShl, checked_shl, u64);
126+
checked_shift_impl!(CheckedShl, checked_shl, usize);
127+
128+
checked_shift_impl!(CheckedShl, checked_shl, i8);
129+
checked_shift_impl!(CheckedShl, checked_shl, i16);
130+
checked_shift_impl!(CheckedShl, checked_shl, i32);
131+
checked_shift_impl!(CheckedShl, checked_shl, i64);
132+
checked_shift_impl!(CheckedShl, checked_shl, isize);
133+
134+
/// Performs a right shift that returns `None` on overflow.
135+
pub trait CheckedShr: Sized + Shr<u32, Output=Self> {
136+
/// Shifts a number to the left, checking for overflow. If overflow happens,
137+
/// `None` is returned.
138+
///
139+
/// ```
140+
/// use num_traits::CheckedShr;
141+
///
142+
/// let x: u16 = 0x8000;
143+
///
144+
/// assert_eq!(CheckedShr::checked_shr(&x, 0), Some(0x8000));
145+
/// assert_eq!(CheckedShr::checked_shr(&x, 1), Some(0x4000));
146+
/// assert_eq!(CheckedShr::checked_shr(&x, 15), Some(0x0001));
147+
/// assert_eq!(CheckedShr::checked_shr(&x, 16), None);
148+
/// ```
149+
fn checked_shr(&self, rhs: u32) -> Option<Self>;
150+
}
151+
152+
checked_shift_impl!(CheckedShr, checked_shr, u8);
153+
checked_shift_impl!(CheckedShr, checked_shr, u16);
154+
checked_shift_impl!(CheckedShr, checked_shr, u32);
155+
checked_shift_impl!(CheckedShr, checked_shr, u64);
156+
checked_shift_impl!(CheckedShr, checked_shr, usize);
157+
158+
checked_shift_impl!(CheckedShr, checked_shr, i8);
159+
checked_shift_impl!(CheckedShr, checked_shr, i16);
160+
checked_shift_impl!(CheckedShr, checked_shr, i32);
161+
checked_shift_impl!(CheckedShr, checked_shr, i64);
162+
checked_shift_impl!(CheckedShr, checked_shr, isize);

0 commit comments

Comments
 (0)