# What numeric traits make sense for std?

For a second, pretend we have GAT, Const Generics, and whatever your favorite type system feature is.

What numeric traits would make sense to have in std? What numeric traits would make sense to not have in std, but in a "2nd party" (i.e. officially endorced) crate? What numeric traits might be useful but can be relegated to a 3rd party crate?

I'm not really concerned with how the traits would be laid out. I'm interested in what functionality you think deserves to be standardized vocabulary for talking about types in programs.

num-traits is the current go-to for numerical traits, of which I'll reproduce a few highlights below:

• `NumOps`: just an alias for `Add`+`Sub`+`Div`+`Mul`
• `Zero`/`One`: additive and multiplicative identity
• `Float`: operations that make sense on a floating point number
• `Real`: mathematical real numbers without floating point oddities required
• `PrimInt`: trivially copyable, two's compliment integers

I personally think that abstractions for higher level mathematical concepts (rings, graphs, etc) aren't generally applicable enough to live in std, but basic numerics feels simple and universal to potentially deserve a spot in std.

2 Likes

If you're really evil, you can currently get the behavior (specified, even!) of `One`/`Zero` by using `Iterator::product`/`sum` on an empty iterator.

3 Likes

Note that most of `num` was in `std::num` before Rust 1.0, but deemed unready for permanent stabilization that `std` requires. So that became the external crate, later split into the separate `num-traits` etc.

2 Likes

Similarly, you can spell `successor(x)` as `(x..).next().unwrap()`

5 Likes

For integers you could also use `From<bool>` instead of `One` and `Zero` though that's not implemented for floats, where you can use `From<i8>` or `From<u8>`.

At the very least, all the methods of primitive number types should be defined in a trait in the standard library. For example, we could have `Pow`, `Abs`, `Signum`, `IsPositive`, `IsNegative`, `MinValue`, `MaxValue` traits, and so on.

Or, we could have bigger traits with more than one method, like `Signed`, `Float`, `Int`, `Bounded`.

`Zero`/`One` is nice to have, although I'm not sure if it's important enough to live in std.

1 Like

They might help with making it possible to use `Iterator::product()` and `Iterator::sum()` on non-std types.

Mind that moving traits to std makes it impossible to add a new required item. `Float`, `Real`, and `PrimInt` contain dozens of required items and it seems likely that we'd want to add new methods in the future.

`NumOps` I feel is too broad. It requires `Mul`, `Div`, and `Rem`, but these three traits satisfy different properties on integers versus floats.

5 Likes

What I find myself doing is writing some traits so that I can write generic code over the integer primitives. What I would find useful is having one large trait implementing all the functions common to the integers, or maybe having three traits: `Int`, `IntSigned: Int` and `IntUnsigned: Int` to handle methods that depend on signedness.

But then I would expect these traits to incorporate any new methods implemented for primitive integers, so they have to be somehow sealed. That means that for the traits I would like, it wouldn't be possible to implement them for external types.

Traits that can be implemented by external types have a different purpose, and it should always be clear whether a trait is intended to cover as much primitive methods as possible, or whether it is intended to be implemented by external types. These aims go against each other, that's why it needs to be clear which of the two kinds is the aim of a trait.

1 Like

Having numeric traits `std` could've also helped to reduce number of methods, e.g. `Duration` has float related methods, and we essentially have to duplicate methods by introducing `f32` and `f64` variants instead of having a single generic version.

4 Likes

which will only get worse if/when Rust introduces `fp16` and/or `fp128`.

I understand there are technical reasons why this isn't yet so but it'd be brilliant to be able to write mathematical code that's generic over:

• floats
• unsigned integers
• signed integers

Unsigned integers in particular only differ from each other in byte length.

1. Lots of very specific traits, similar to the current `Neg`, `Add`, `BitAndAssign`, etc. These would be for example `trait Abs`, `trait FromStrRadix`, `trait CheckedAdd` and so on. These would already allow code to be generic over primitive integers, as all you would need to do is something like `fn foo<I>(i: I) where I: CheckedAdd<Output = I> + Abs<Output = I>`.

2. Not all traits would need to have just a single method. For example `CheckedAdd` could easily be

``````trait CheckedAdd<Rhs = Self> {
type Output;
fn checked_add(self, rhs: Rhs) -> Option<Self::Output> {
if overflow { None } else { Some(val) }
}
fn saturating_add(self, rhs: Rhs) -> Self::Output;
fn wrapping_add(self, rhs: Rhs) -> Self::Output {
3. As a convenience feature, there could be some very broad traits, which should probably be sealed. They would have all the other relevant little specific traits as supertraits. That way someone could just write something like `fn foo<I: Int>(i: I)` and use all the features common to all integers. I'm not sure these need to be in `std` though, they could easily be in a crate.