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.

7 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!

6 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>.

2 Likes

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.

7 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.

7 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}>;
3 Likes

That same feature allows to impl PrimitiveInt for usize {} and impl PrimitiveInt for isize {} separately, doesn't it?

A better solution would be if modules could export opaque definitions:

#[lang = "generic_unsigned"]
pub struct Unsigned<const SIZE: usize> { ... }

existence_pub_but_definition_priv const USIZE_BITS: usize = ... ;

pub type usize = Unsigned<USIZE_BITS>;

That would also give us both sealed traits and the ability to export enums/unions/type-aliases without having to wrap them in a struct.

Yeah, I've referred to this as "impl usize" before: the same way I want to return "some type that's Ord but I don't want to commit to which" sometimes, I also want the capability to return "some usize but I don't want to commit to which".

Notably, pub const FOO: [u8; impl const usize] = include_bytes!("data.bin"); would be nice because I probably don't want a semver commitment that the array will stay exactly that long forever.

1 Like

Similarly, UNICODE_VERSION has changed before and will change again in the future.

1 Like

It's interesting to note that even in the case of opaque const values, downstream can still rely on the precise value indirectly if it can inspect the value in any way from const. There's passive acceptance that relying on the value of a const comparison to be strictly stable is ill-advised which doesn't apply to item const as strongly.

So it'd need to be something like #[non_exhaustive] where downstream is constrained (can't even use the const in const contexts) while the defining crate isn't, if it's to make changing the value into a strictly semver-minor change, unless you extend the entire ecosystem's use of const parameters to work with symbolic consts where possible.

usize is perhaps the one case where it'd be useful symbolically because you can strengthen the bound from ?Sized on [_; #[symbolic] usize] to Sized but without const on size_of. (Today's Sized means const Sized, effectively.)


A counterpoint to this whole thing, though: while we don't allow e.g. type aliases to change between versions, we do allow them to change between targets. Even though that clearly can break code. It's always been a bit odd to me that we specifically don't make usize a varrying type alias to make code unable to directly rely on usize = u64, but we allow it for the stdlib c_type aliases. I think variable const are just another case of this concession to practicality over academic purity.

(Note that this thread is in the wrong category: this is about the standard library, not about "language design".)