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.
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.
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!
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?
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'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.
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>.
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.
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.
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.
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.