"Truncate" has been discussed earlier, but I don't think it's at all suitable for narrowing (or other) casts between integers. At least to me, the word specifically means removing (zeroing) least significant digits. I think "wrapping" is a much better word for this operation.
That still sounds very surprising. It would be as if NonZeroU8
were numbers modulo 255, but with the unusual convention of denoting 0 by 255 instead.
Tangent on modular arithmetic and Rust integers
So, with wrapping arithmetic the numeric types uN and iN model integers modulo 2^N. In this sense and with the two's complement representation used by Rust, iN is equivalent to uN: for any x,y: uN
, (x + y) as iN == x as iN + y as iN
(with any of +,-,* used as a wrapping operator). The signedness is just a selection of which of up to two possible representatives is used for other operations where it does matter.
Considering both widening and narrowing casts (signed or not), x as {u,i}N
is just returning the numeric value of x as a number modulo 2^N. As a particular example, -1i8 as u16 == u16::MAX
, because u16::MAX
is the unique number in the range of u16 that is congruent to -1 (mod 2^16). Admittedly a cast like this (widening cast from signed to unsigned) is the most ambiguous kind since it could plausibly be defined as first casting to the unsigned type of the same width.
With that mental model for primitive integers in Rust, none of the as
-casts between integers are of much concern if the types are known.
I wonder which of these are clearer?
let x: u32 = ...;
foo(x as usize);
// OR
foo(x.into());
bar(x as u16);
// OR
bar(x.wrapping_into());
I'm aware it could also be x as _
, or into
could be turbofished (and would often have to be), but the point here is that I feel with as
the type is more likely to be written explicitly. In part precisely because as
is capable of many different types of casting.
Clearly programmer intent is what is missing with as
-casts. It's unclear if x as u16
is supposed to wrap, or known not to. Then again, that's also the case with wrapping_into
, in case the programmer knew it won't wrap, and wanted to avoid the check.