Since the num
crate has been deprecated and generic traits are the most frequently used parts, I developed a small crate num-traits
to fill this gap. It is an implementation of the num-reform
RFC, which only provides six primitive traits (Int
, UnsignedInt
, Signed
, Float
, Zero
, One
) to cover most use cases for generic mathematics. Zero
and One
are separated because they have been included in the std::num
.
A big difference from the num
crate is the implementation of an interface for casting numbers. The num
crate introduces FromPrimitive
, ToPrimitive
, and NumCast
traits, whereas we only use a CastInto
trait as an equivelence to Into<Option<T>>
. It will be unnecessary if these traits can be included in std::num
.
The crate is experimental and it is not on crates.io
. Any comments are welcome!
Regarding the casting traits, there's also my conv
crate. It looks like CastInto
is a combination of ApproxInto
and ValueInto
, without any error types to tell you why a conversion failed, or recover the original input (so it's less than ideal for non-Copy
types).
Oh, another advantage: ValueInto
can statically tell when a conversion cannot possibly fail, allowing you to use a code path that cannot possibly panic, which might make for better code generation.
Thanks for the link. I will check that.
The CastInto
is only a simple wrapper for as
associated with checking overflow of the type. If the number is beyond the range, it returns a None
. For casting primitive numbers, this is enough, since overflow is the only reason to cause an error; for the extension to other types, returning a Result
is indeed a better choice.
Even with integer types, Result
is useful as it allows you to distinguish between the source being too large or too small to fit into the destination range. This in turn allows for things like an_i16.value_as::<i8>().unwrap_or_saturate()
, which will clamp the result appropriately: you can't do that if all you have is None
.
Also, you return Option
in cases where it's impossible for the conversion to fail, like u16
→ u8
. This leads to people adding .unwrap()
, which is fine until they refactor something and suddenly the conversion can fail, but they've now masked that. That's why conv
has NoError
and a trait defining .unwrap_ok()
so that you can statically assert that a conversion cannot fail without needing a different trait.
Option
also doesn't allow you to distinguish between range violation and... whatever the hell you'd call trying to convert NaN into an integer.
Thanks for your comments. I will consider changing it to a Result
. It can provide more error information and more extensibility.