diff --git a/active/0000-num-reform.md b/active/0000-num-reform.md new file mode 100644 index 00000000000..119ae8c2563 --- /dev/null +++ b/active/0000-num-reform.md @@ -0,0 +1,432 @@ +- Start Date: (fill me in with today's date, 2014-09-16) +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +This RFC is preparation for API stabilization for the `std::num` module. The +proposal is to finish the simplification efforts started in +[@bjz's reversal of the numerics hierarcy](https://github.com/rust-lang/rust/issues/10387). + +Broadly, the proposal is to collapse the remaining numeric hierarchy +in `std::num`, and to provide only limited support for generic +programming (roughly, only over primitive numeric types that vary +based on size). Traits giving detailed numeric hierarchy can and +should be provided separately through the Cargo ecosystem. + +Thus, this RFC proposes to flatten or remove most of the traits +currently provided by `std::num`, and generally to simplify the module +as much as possible in preparation for API stabilization. + +# Motivation + +## History + +Starting in early 2013, there was +[an effort](https://github.com/rust-lang/rust/issues/4819) to design a +comprehensive "numeric hierarchy" for Rust: a collection of traits classifying a +wide variety of numbers and other algebraic objects. The intent was to allow +highly-generic code to be written for algebraic structures and then instantiated +to particular types. + +This hierarchy covered structures like bigints, but also primitive integer and +float types. It was an enormous and long-running community effort. + +Later, [it was recognized](https://github.com/rust-lang/rust/issues/10387) that +building such a hierarchy within `libstd` was misguided: + +> @bjz The API that resulted from #4819 attempted, like Haskell, to blend both +> the primitive numerics and higher level mathematical concepts into one +> API. This resulted in an ugly hybrid where neither goal was adequately met. I +> think the libstd should have a strong focus on implementing fundamental +> operations for the base numeric types, but no more. Leave the higher level +> concepts to libnum or future community projects. + +The `std::num` module has thus been slowly migrating *away* from a large trait +hierarchy toward a simpler one providing just APIs for primitive data types: +this is +[@bjz's reversal of the numerics hierarcy](https://github.com/rust-lang/rust/issues/10387). + +Along side this effort, there are already external numerics packages like +[@bjz's num-rs](https://github.com/bjz/num-rs). + +But we're not finished yet. + +## The current state of affairs + +The `std::num` module still contains quite a few traits that subdivide out +various features of numbers: + +```rust +pub trait Zero: Add { + fn zero() -> Self; + fn is_zero(&self) -> bool; +} + +pub trait One: Mul { + fn one() -> Self; +} + +pub trait Signed: Num + Neg { + fn abs(&self) -> Self; + fn abs_sub(&self, other: &Self) -> Self; + fn signum(&self) -> Self; + fn is_positive(&self) -> bool; + fn is_negative(&self) -> bool; +} + +pub trait Unsigned: Num {} + +pub trait Bounded { + fn min_value() -> Self; + fn max_value() -> Self; +} + +pub trait Primitive: Copy + Clone + Num + NumCast + PartialOrd + Bounded {} + +pub trait Num: PartialEq + Zero + One + Neg + Add + Sub + + Mul + Div + Rem {} + +pub trait Int: Primitive + CheckedAdd + CheckedSub + CheckedMul + CheckedDiv + + Bounded + Not + BitAnd + BitOr + + BitXor + Shl + Shr { + fn count_ones(self) -> uint; + fn count_zeros(self) -> uint { ... } + fn leading_zeros(self) -> uint; + fn trailing_zeros(self) -> uint; + fn rotate_left(self, n: uint) -> Self; + fn rotate_right(self, n: uint) -> Self; + fn swap_bytes(self) -> Self; + fn from_be(x: Self) -> Self { ... } + fn from_le(x: Self) -> Self { ... } + fn to_be(self) -> Self { ... } + fn to_le(self) -> Self { ... } +} + +pub trait FromPrimitive { + fn from_i64(n: i64) -> Option; + fn from_u64(n: u64) -> Option; + + // many additional defaulted methods + // ... +} + +pub trait ToPrimitive { + fn to_i64(&self) -> Option; + fn to_u64(&self) -> Option; + + // many additional defaulted methods + // ... +} + +pub trait NumCast: ToPrimitive { + fn from(n: T) -> Option; +} + +pub trait Saturating { + fn saturating_add(self, v: Self) -> Self; + fn saturating_sub(self, v: Self) -> Self; +} + +pub trait CheckedAdd: Add { + fn checked_add(&self, v: &Self) -> Option; +} + +pub trait CheckedSub: Sub { + fn checked_sub(&self, v: &Self) -> Option; +} + +pub trait CheckedMul: Mul { + fn checked_mul(&self, v: &Self) -> Option; +} + +pub trait CheckedDiv: Div { + fn checked_div(&self, v: &Self) -> Option; +} + +pub trait Float: Signed + Primitive { + // a huge collection of static functions (for constants) and methods + ... +} + +pub trait FloatMath: Float { + // an additional collection of methods +} +``` + +The `Primitive` traits are intended primarily to support a mechanism, +`#[deriving(FromPrimitive)]`, that makes it easy to provide +conversions from numeric types to C-like `enum`s. + +The `Saturating` and `Checked` traits provide operations that provide +special handling for overflow and other numeric errors. + +Almost all of these traits are currently included in the prelude. + +In addition to these traits, the `std::num` module includes a couple +dozen free functions, most of which duplicate methods available though +traits. + +## Where we want to go: a summary + +The goal of this RFC is to refactor the `std::num` hierarchy with the +following goals in mind: + +* Simplicity. + +* *Limited* generic programming: being able to work generically over + the natural classes of *primitive* numeric types that vary only by + size. There should be enough abstraction to support porting + `strconv`, the generic string/number conversion code used in `std`. + +* Minimizing dependencies for `libcore`. For example, it should not + require `cmath`. + +* Future-proofing for external numerics packages. The Cargo ecosystem + should ultimately provide choices of sophisticated numeric + hierarchies, and `std::num` should not get in the way. + +# Detailed design + +## Overview: the new hierarchy + +This RFC proposes to collapse the trait hierarchy in `std::num` to +just the following traits: + +* `Int`, implemented by all primitive integer types (`u8` - `u64`, `i8`-`i64`) + * `UnsignedInt`, implemented by `u8` - `u64` +* `Signed`, implemented by all signed primitive numeric types (`i8`-`i64`, `f32`-`f64`) +* `Float`, implemented by `f32` and `f64` + * `FloatMath`, implemented by `f32` and `f64`, which provides functionality from `cmath` + +These traits inherit from all applicable overloaded operator traits +(from `core::ops`). They suffice for generic programming over several +basic categories of primitive numeric types. + +As designed, these traits include a certain amount of redundancy +between `Int` and `Float`. The Alternatives section shows how this +could be factored out into a separate `Num` trait. But doing so +suggests a level of generic programming that these traits aren't +intended to support. + +The main reason to pull out `Signed` into its own trait is so that it +can be added to the prelude. (Further discussion below.) + +## Detailed definitions + +Below is the full definition of these traits. The functionality +remains largely as it is today, just organized into fewer traits: + +```rust +pub trait Int: Copy + Clone + PartialOrd + PartialEq + + Add + Sub + + Mul + Div + Rem + + Not + BitAnd + BitOr + + BitXor + Shl + Shr +{ + // Constants + fn zero() -> Self; // These should be associated constants when those are available + fn one() -> Self; + fn min_value() -> Self; + fn max_value() -> Self; + + // Deprecated: + // fn is_zero(&self) -> bool; + + // Bit twidling + fn count_ones(self) -> uint; + fn count_zeros(self) -> uint { ... } + fn leading_zeros(self) -> uint; + fn trailing_zeros(self) -> uint; + fn rotate_left(self, n: uint) -> Self; + fn rotate_right(self, n: uint) -> Self; + fn swap_bytes(self) -> Self; + fn from_be(x: Self) -> Self { ... } + fn from_le(x: Self) -> Self { ... } + fn to_be(self) -> Self { ... } + fn to_le(self) -> Self { ... } + + // Checked arithmetic + fn checked_add(self, v: Self) -> Option; + fn checked_sub(self, v: Self) -> Option; + fn checked_mul(self, v: Self) -> Option; + fn checked_div(self, v: Self) -> Option; + fn saturating_add(self, v: Self) -> Self; + fn saturating_sub(self, v: Self) -> Self; +} + +pub trait UnsignedInt: Int { + fn is_power_of_two(self) -> bool; + fn checked_next_power_of_two(self) -> Option; + fn next_power_of_two(self) -> Self; +} + +pub trait Signed: Neg { + fn abs(&self) -> Self; + fn signum(&self) -> Self; + fn is_positive(&self) -> bool; + fn is_negative(&self) -> bool; + + // Deprecated: + // fn abs_sub(&self, other: &Self) -> Self; +} + +pub trait Float: Copy + Clone + PartialOrd + PartialEq + Signed + + Add + Sub + + Mul + Div + Rem +{ + // Constants + fn zero() -> Self; // These should be associated constants when those are available + fn one() -> Self; + fn min_value() -> Self; + fn max_value() -> Self; + + // Classification and decomposition + fn is_nan(self) -> bool; + fn is_infinite(self) -> bool; + fn is_finite(self) -> bool; + fn is_normal(self) -> bool; + fn classify(self) -> FPCategory; + fn integer_decode(self) -> (u64, i16, i8); + + // Float intrinsics + fn floor(self) -> Self; + fn ceil(self) -> Self; + fn round(self) -> Self; + fn trunc(self) -> Self; + fn mul_add(self, a: Self, b: Self) -> Self; + fn sqrt(self) -> Self; + fn powi(self, n: i32) -> Self; + fn powf(self, n: Self) -> Self; + fn exp(self) -> Self; + fn exp2(self) -> Self; + fn ln(self) -> Self; + fn log2(self) -> Self; + fn log10(self) -> Self; + + // Conveniences + fn fract(self) -> Self; + fn recip(self) -> Self; + fn rsqrt(self) -> Self; + fn to_degrees(self) -> Self; + fn to_radians(self) -> Self; + fn log(self, base: Self) -> Self; +} + +// This lives directly in `std::num`, not `core::num`, since it requires `cmath` +pub trait FloatMath: Float { + // Exactly the methods defined in today's version +} +``` + +## Float constants, float math, and `cmath` + +This RFC proposes to: + +* Remove all float constants from the `Float` trait. These constants + are available directly from the `f32` and `f64` modules, and are not + really useful for the kind of generic programming these new traits + are intended to allow. + +* Continue providing various `cmath` functions as methods in the + `FloatMath` trait. Putting this in a separate trait means that + `libstd` depends on `cmath` but `libcore` does not. + +## Free functions + +All of the free functions defined in `std::num` are deprecated. + +## The prelude + +The prelude will only include the `Signed` trait, as the operations it +provides are widely expected to be available when they apply. + +The reason for removing the rest of the traits is two-fold: + +* The remaining operations are relatively uncommon. Note that various + overloaded operators, like `+`, work regardless of this choice. + Those doing intensive work with e.g. floats would only need to + import `Float` and `FloatMath`. + +* Keeping this functionality out of the prelude means that the names + of methods and associated items remain available for external + numerics libraries in the Cargo ecosystem. + +## `strconv`, `FromStr`, `ToStr`, `FromStrRadix`, `ToStrRadix` + +Currently, traits for converting from `&str` and to `String` are both +included, in their own modules, in `libstd`. This is largely due to +the desire to provide `impl`s for numeric types, which in turn relies +on `std::num::strconv`. + +This RFC proposes to: + +* Move the `FromStr` trait into `core::str`. +* Rename the `ToStr` trait to `ToString`, and move it to `collections::string`. +* Break up and revise `std::num::strconv` into separate, *private* + modules that provide the needed functionality for the `from_str` and + `to_string` methods. (Some of this functionality has already + migrated to `fmt` and been deprecated in `strconv`.) +* Move the `FromStrRadix` into `core::num`. +* Remove `ToStrRadix`, which is already deprecated in favor of `fmt`. + +## `FromPrimitive` and friends + +Ideally, the `FromPrimitive`, `ToPrimitive`, `Primitive`, `NumCast` +traits would all be removed in favor of a more principled way of +working with C-like enums. However, such a replacement is outside of +the scope of this RFC, so these traits are left (as `#[experimental]`) +for now. A follow-up RFC proposing a better solution should appear soon. + +In the meantime, see +[this proposal](https://github.com/rust-lang/rust/issues/10418) and +the discussion on +[this issue](https://github.com/rust-lang/rust/issues/10272) about +`Ordinal` for the rough direction forward. + +# Drawbacks + +This RFC somewhat reduces the potential for writing generic numeric +code with `std::num` traits. This is intentional, however: the new +design represents "just enough" generics to cover differently-sized +built-in types, without any attempt at general algebraic abstraction. + +# Alternatives + +The status quo is clearly not ideal, and as explained above there was +a long attempt at providing a more complete numeric hierarchy in `std`. +So *some* collapse of the hierarchy seems desirable. + +That said, there are other possible factorings. We could introduce the +following `Num` trait to factor out commonalities between `Int` and `Float`: + +```rust +pub trait Num: Copy + Clone + PartialOrd + PartialEq + + Add + Sub + + Mul + Div + Rem +{ + fn zero() -> Self; // These should be associated constants when those are available + fn one() -> Self; + fn min_value() -> Self; + fn max_value() -> Self; +} +``` + +However, it's not clear whether this factoring is worth having a more +complex hierarchy, especially because the traits are not intended for +generic programming at that level (and generic programming across +integer and floating-point types is likely to be extremely rare) + +The signed and unsigned operations could be offered on more types, +allowing removal of more traits but a less clear-cut semantics. + + +# Unresolved questions + +This RFC does not propose a replacement for +`#[deriving(FromPrimitive)]`, leaving the relevant traits in limbo +status. (See +[this proposal](https://github.com/rust-lang/rust/issues/10418) and +the discussion on +[this issue](https://github.com/rust-lang/rust/issues/10272) about +`Ordinal` for the rough direction forward.)