The purpose of these traits is exclusively scoped to abstracting over the sets of primitive types which already exist in the language for numeric literal type inference β namely, {integer}
and {float}
. Attempting to generalize further to include non-primitive arithmetic types is an explicit non-goal left to ecosystem crates like num-traits. Further refinement and/or extension of the trait hierarchy (e.g. a trait covering all numeric primitives, or traits for just signed or unsigned integers) are deferred for the time being and left to ecosystem crates such as funty to provide.
Why? Rust code has gotten along perfectly fine without such traits so far, either by code just using a large enough numeric type instead of being generic, or by using a macro to stamp out a trait implementation across multiple primitive types. A primary reason is pedagogy and optics; it's a very easy desire to have, seeing the multiple numeric types, to write code that works for any of them. Having to fall back to the hammer that is syntactic macros to accomplish what seems on the surface like a relatively simple ask doesn't feel great. And people do want to write such code in practice; a look at the reverse dependencies of the aforementioned crates gives a partial picture of the crates doing so with generics and missing crates doing so with internally defined macros.
To the core::primitive
module, we add two traits.
(Edit context: Removed a shared Primitive
supertrait which was sealed and implemented for the types reexported in the core::primitive
module. Discussion here reached consensus that it wasn't adding anything worthwhile.)
Sealing is done as a matter of future extensibility. By sealing the primitive abstraction traits, we can add new functionality onto the traits in a nonbreaking manner to mirror new functionality added to the primitive types themselves.
All of the core::primitive
traits are #[fundamental]
, meaning that downstream implementations can rely on types other than the primitive types not implementing the primitive abstraction traits. Notably, this would hopefully allow downstream blanket impls over T: Integer
and T: Float
to not conflict.
#[sealed]
#[fundamental]
pub trait Integer: 'static
+ Sized + Copy + Clone
+ Send + Sync + Unpin
+ UnwindSafe + RefUnwindSafe
+ // every trait impl macro-pasted for all integers
{
// every associated item macro-pasted for all integers
}
impl Integer for i8 { /* ... */ }
impl Integer for i16 { /* ... */ }
impl Integer for i32 { /* ... */ }
impl Integer for i64 { /* ... */ }
impl Integer for i128 { /* ... */ }
impl Integer for isize { /* ... */ }
impl Integer for u8 { /* ... */ }
impl Integer for u16 { /* ... */ }
impl Integer for u32 { /* ... */ }
impl Integer for u64 { /* ... */ }
impl Integer for u128 { /* ... */ }
impl Integer for usize { /* ... */ }
#[sealed]
#[fundamental]
pub trait Float: 'static
+ Sized + Copy + Clone
+ Send + Sync + Unpin
+ UnwindSafe + RefUnwindSafe
+ // every trait impl macro-pasted for all floats
{
// every associated item macro-pasted for all floats
}
impl Float for f32 { /* ... */ }
impl Float for f64 { /* ... */ }
To reiterate, the set of functionality provided by these traits is intended to be dynamic and grow as more functionality is added to primitive integers/floats. The one small wrinkle is that the functionality provided should not prevent the addition of new primitive arithmetic types (e.g. f16
, f128
, u256
) in the future, should those be desired.
An additional possibility is to replace the current definition of unsafe fn f32::to_int_unchecked<Int>(self) -> Int where Self: FloatToInt<Int>
with a more usual looking unsafe fn f32::to_int_unchecked<Int: Integer>(self) -> Int
, though FloatToInt
would likely remain as an an implementation detail. Similarly, other rustdoc signatures (and implementors lists) could be simplified to use these traits instead of listing every component type separately.
If we want to be extra careful and reserve the right to move some items onto a supertrait instead (e.g. a shared primitive::Numeric
trait[1] or some ops::WrappingOp
trait families), it should be possible to put the items on an unnameable supertrait, such that they can be used with method syntax or Type::item
, but not Trait::item
. However, the items suggested to live on this trait do so because they are inherent items already, and must stay so for stability reasons; thus the RFC author believes that putting them on the trait should not add any additional stability concerns. The only functionality shared between {integer}
and {float}
is as
interconversion (which isn't available by method yet) and interconversion to byte arrays (which isn't provided on the trait yet as doing so is dependent on incomplete nightly features). Supertraits can be moved onto a shared supertrait backwards compatibly.
Additionally, the fact that these traits are (deliberately) not object safe improves their forward compatibility to extension, e.g. by not having to consider the exact potential stability implications of trait object upcasting.
Potential full trait definitions
pub trait Integer: 'static
// marker
+ Sized + Copy + Clone
+ Send + Sync + Unpin
+ UnwindSafe + RefUnwindSafe
// fmt
+ Debug + Display + fmt::Binary + fmt::Octal
+ fmt::LowerHex + fmt::UpperHex + fmt::LowerExp + fmt::UpperExp
// misc
+ Default + PartialEq + Eq + PartialOrd + Ord + Hash
// conv
+ FromStr
+ for<Int: Integer> (TryFrom<Int> + TryInto<Int>)
+ for<Flt: Float> TryInto<Flt>
// arith ops
+ ops::Add<Self, Output = Self> + for<'a> ops::Add<&'a Self, Output = Self>
+ ops::Sub<Self, Output = Self> + for<'a> ops::Sub<&'a Self, Output = Self>
+ ops::Mul<Self, Output = Self> + for<'a> ops::Mul<&'a Self, Output = Self>
+ ops::Div<Self, Output = Self> + for<'a> ops::Div<&'a Self, Output = Self>
+ ops::Rem<Self, Output = Self> + for<'a> ops::Rem<&'a Self, Output = Self>
+ ops::AddAssign<Self> + for<'a> ops::AddAssign<&'a Self>
+ ops::SubAssign<Self> + for<'a> ops::SubAssign<&'a Self>
+ ops::MulAssign<Self> + for<'a> ops::MulAssign<&'a Self>
+ ops::DivAssign<Self> + for<'a> ops::DivAssign<&'a Self>
+ ops::RemAssign<Self> + for<'a> ops::RemAssign<&'a Self>
// bit ops
+ ops::Not
+ ops::BitAnd<Self, Output = Self> + for<'a> ops::BitAnd<&'a Self, Output = Self>
+ ops::BitOr<Self, Output = Self> + for<'a> ops::BitOr<&'a Self, Output = Self>
+ ops::BitXor<Self, Output = Self> + for<'a> ops::BitXor<&'a Self, Output = Self>
+ for<Int: Integer> (ops::Shl<Int, Output = Self> + for<'a> ops::Shl<&'a Int, Output = Self>)
+ for<Int: Integer> (ops::Shr<Int, Output = Self> + for<'a> ops::Shr<&'a Int, Output = Self>)
+ ops::BitAndAssign<Self> + for<'a> ops::BitAndAssign<&'a Self>
+ ops::BitOrAssign<Self> + for<'a> ops::BitAndAssign<&'a Self>
+ ops::BitXorAssign<Self> + for<'a> ops::BitAndAssign<&'a Self>
+ for<Int: Integer> (ops::ShlAssign<Int> + for<'a> ops::ShlAssign<&'a Int>)
+ for<Int: Integer> (ops::ShrAssign<Int> + for<'a> ops::ShrAssign<&'a Int>)
// iter
+ iter::Sum<Self> + for<'a> iter::Sum<&'a Self>
+ iter::Product<Self> + for<'a> iter::Product<&'a Self>
// (only include the where bound if it's automatically elaborated)
where
for<'a, 'b> &'a Self: Sized
// arith ops
+ ops::Add<&'b Self, Output = Self>
+ ops::Sub<&'b Self, Output = Self>
+ ops::Mul<&'b Self, Output = Self>
+ ops::Div<&'b Self, Output = Self>
+ ops::Rem<&'b Self, Output = Self>
// bit ops
+ ops::Not<Output = Self>
+ ops::BitAnd<&'b Self, Output = Self>
+ ops::BitOr<&'b Self, Output = Self>
+ ops::BitXor<&'b Self, Output = Self>
+ for<Int: Integer> ops::Shl<&'b Int, Output = Self>
+ for<Int: Integer> ops::Shr<&'b Int, Output = Self>
{
// Actually, the int_impl! and uint_impl! don't deduplicate a common
// subset, so by the definition above, this would currently be empty.
// (This gives a nicer rustdoc item order.)
// In practice, this should encompass, ignoring unstable:
const MIN: Self;
const MAX: Self;
const BITS: u32;
fn from_str_radix(src: &str, radix: u32) -> Result<Self, ParseIntError>;
fn count_ones(self) -> u32;
fn count_zeros(self) -> u32;
fn leading_zeros(self) -> u32;
fn trailing_zeros(self) -> u32;
fn leading_ones(self) -> u32;
fn trailing_ones(self) -> u32;
fn rotate_left(self, n: u32) -> Self;
fn rotate_right(self, n: u32) -> Self;
fn swap_bytes(self) -> Self;
fn reverse_bits(self) -> Self;
fn from_be(x: Self) -> Self;
fn from_le(x: Self) -> Self;
fn to_be(self) -> Self;
fn to_le(self) -> Self;
fn checked_add(self, rhs: Self) -> Option<Self>;
fn checked_sub(self, rhs: Self) -> Option<Self>;
fn checked_mul(self, rhs: Self) -> Option<Self>;
fn checked_div(self, rhs: Self) -> Option<Self>;
fn checked_div_euclid(self, rhs: Self) -> Option<Self>;
fn checked_rem(self, rhs: Self) -> Option<Self>;
fn checked_rem_euclid(self, rhs: Self) -> Option<Self>;
fn checked_neg(self) -> Option<Self>;
fn checked_shl(self) -> Option<Self>;
fn checked_shr(self) -> Option<Self>;
fn checked_pow(self, exp: u32) -> Option<Self>;
fn saturating_add(self, rhs: Self) -> Option<Self>;
fn saturating_sub(self, rhs: Self) -> Option<Self>;
fn saturating_mul(self, rhs: Self) -> Option<Self>;
fn saturating_div(self, rhs: Self) -> Option<Self>;
fn saturating_pow(self, exp: u32) -> Option<Self>;
fn wrapping_add(self, rhs: Self) -> Option<Self>;
fn wrapping_sub(self, rhs: Self) -> Option<Self>;
fn wrapping_mul(self, rhs: Self) -> Option<Self>;
fn wrapping_div(self, rhs: Self) -> Option<Self>;
fn wrapping_div_euclid(self, rhs: Self) -> Option<Self>;
fn wrapping_rem(self, rhs: Self) -> Option<Self>;
fn wrapping_rem_euclid(self, rhs: Self) -> Option<Self>;
fn wrapping_neg(self) -> Option<Self>;
fn wrapping_shl(self) -> Option<Self>;
fn wrapping_shr(self) -> Option<Self>;
fn wrapping_pow(self, exp: u32) -> Option<Self>;
fn overflowing_add(self, rhs: Self) -> (Self, bool);
fn overflowing_sub(self, rhs: Self) -> (Self, bool);
fn overflowing_mul(self, rhs: Self) -> (Self, bool);
fn overflowing_div(self, rhs: Self) -> (Self, bool);
fn overflowing_div_euclid(self, rhs: Self) -> (Self, bool);
fn overflowing_rem(self, rhs: Self) -> (Self, bool);
fn overflowing_rem_euclid(self, rhs: Self) -> (Self, bool);
fn overflowing_neg(self, rhs: Self) -> (Self, bool);
fn overflowing_shl(self, rhs: Self) -> (Self, bool);
fn overflowing_shr(self, rhs: Self) -> (Self, bool);
fn overflowing_pow(self, exp: u32) -> (Self, bool);
fn pow(self, exp: u32) -> Self;
fn div_euclid(self, rhs: Self) -> Self;
fn rem_euclid(self, rhs: Self) -> Self;
fn div_floor(self, rhs: Self) -> Self;
fn div_ceil(self, rhs: Self) -> Self;
fn ilog(self, rhs: Self) -> u32;
fn ilog2(self, rhs: Self) -> u32;
fn ilog10(self, rhs: Self) -> u32;
fn checked_ilog(self, rhs: Self) -> Option<u32>;
fn checked_ilog2(self, rhs: Self) -> Option<u32>;
fn checked_ilog10(self, rhs: Self) -> Option<u32>;
fn min_value() -> Self;
fn max_value() -> Self;
}
pub trait Float: 'static
// marker
+ Sized + Copy + Clone
+ Send + Sync + Unpin
+ UnwindSafe + RefUnwindSafe
// fmt
+ Debug + Display + fmt::LowerExp + fmt::UpperExp
// misc
+ Default + PartialEq + PartialOrd
// conv
+ FromStr + for<Int: Integer> TryFrom<Int>
// arith ops
+ ops::Add<Self, Output = Self> + for<'a> ops::Add<&'a Self, Output = Self>
+ ops::Sub<Self, Output = Self> + for<'a> ops::Sub<&'a Self, Output = Self>
+ ops::Mul<Self, Output = Self> + for<'a> ops::Mul<&'a Self, Output = Self>
+ ops::Div<Self, Output = Self> + for<'a> ops::Div<&'a Self, Output = Self>
+ ops::Rem<Self, Output = Self> + for<'a> ops::Rem<&'a Self, Output = Self>
+ ops::Neg<Output = Self>,
+ ops::AddAssign<Self> + for<'a> ops::AddAssign<&'a Self>
+ ops::SubAssign<Self> + for<'a> ops::SubAssign<&'a Self>
+ ops::MulAssign<Self> + for<'a> ops::MulAssign<&'a Self>
+ ops::DivAssign<Self> + for<'a> ops::DivAssign<&'a Self>
+ ops::RemAssign<Self> + for<'a> ops::RemAssign<&'a Self>
// iter
+ iter::Sum<Self> + for<'a> iter::Sum<&'a Self>
+ iter::Product<Self> + for<'a> iter::Product<&'a Self>
// (only include the where bound if it's automatically elaborated)
where
for<'a, 'b> &'a Self: Sized
// arith ops
+ ops::Add<&'b Self, Output = Self>
+ ops::Sub<&'b Self, Output = Self>
+ ops::Mul<&'b Self, Output = Self>
+ ops::Div<&'b Self, Output = Self>
+ ops::Rem<&'b Self, Output = Self>
+ ops::Neg<Output = Self>
{
// Actually, the float impls aren't macro pasted, so by
// the definition above, this would currently be empty.
// In practice, the entire impl body is available for both.
// I've not replicated it as doing so confers no benefit.
}
It is most likely that such a trait would only be able to contribute ops trait bounds and the
[to|from]_[n|b|l]e_bytes
methods; there's no other current associated item overlap between{integer}
and{float}
types. EvenMIN
/MAX
have slightly different semantics for the two type sets. β©οΈ