There should be a primitive int / float trait in core / stdlib

This would be useful in cases were you want to use / store a number but don't care about a specific type of number for example:

/// add two integers together
pub fn add<T: PrimitiveInt>(x: T, y: T) -> T {
    x + y
}

This could also be useful for implementing arithmetic operations between primitive ints and "big int" structs:

use std::ops::Add;
struct BigInt {/* */}
impl<T: PrimitiveInt> Add for BigInt {
    // ...
}

And I am aware of solutions provided by for example the num crate. I still however think that this should be added to the stdlib, because there have been over 200 million downloads of the num crate and there are perhaps people that only use the crate for this specific trait. Implementing it would be very easy as it is basically just one huge super-trait that covers all trait which a primitive int implements.

1 Like

Obligatory link to https://github.com/rust-lang/rfcs/pull/3686 which I like because it'll let you do impl<const N: u32> MyTrait for u<N> { ... } or fn my_fn<const N: u32>(i: u<N>), which I think actually covers things better than a trait would because it doesn't have the same coherence problems.

5 Likes

See also num-primitive, which goes well beyond the core num crates by adding all stable functionality that it can for all primitive numeric types. Funny though, it didn't occur to me to start using that in num-bigint for it's primitive interactions, but we probably could!

4 Likes

Wait does the num crate really define big int - primitive interactions per primitive? Wouldn’t it be better if they used their own primitive int trait?

This could also work, but you would still need to implement traits for every int instead of just for all ints

It is what it is, and it could be better...

Remember that if you write

impl<T: MagicNewNumTrait> MyTrait for T { ... }

That's a blanket impl, so you typically can't actually implement MyTrait in any other way -- after all, for all the compiler knows, any type on which you might implement it could also implement MagicNewNumTrait in the future.

That makes it mostly useless in many, many cases.

That's one reason why first-class trait sealing would be nice, so the compiler could know that any type downstream of the trait will never implement it.

1 Like

Yeah, I agree coherence-aware sealing would be interesting -- though complex since the local impls might themselves be dependent on external traits -- for lots of things.

But even if we had that, I still like impl<N> MyTrait for u<N> better. If nothing else because it doesn't also need mutually-exclusive traits to be able to also impl<N> MyTrait for i<N>.

1 Like

Note that this does not solve the problem fully, since even if we alias the existing integers to u<>, i<>, we cannot alias usize and isize.

We're planning to add numeric traits in the standard library once we have library trait evolution, so that we're confident we will be able to fix and extend those traits.

4 Likes

Why not?

#[cfg(target_pointer_width = "64")]
pub type usize = u<64>;

#[cfg(target_pointer_width = "32")]
pub type usize = u<32>;

because usize is never the same type as u32, u64, etc. even if they have the same signedness and bit width and ABI. Rust currently guarantees you can impl traits differently for them and that doesn't work if usize is just a type alias.

2 Likes

That's a solvable problem with expected features, though.

enum IntegerSize {
    Pointer,
    Bits(u32),
}

pub struct UnsignedInternal<const INTEGER_SIZE: IntegerSize>(...);
pub type Unsigned<const N: u32> = UnsignedInternal<{IntegerSize::Bits(N)}>;
pub type usize = UnsignedInternal<{IntegerSize::Pointer}>;
1 Like