Something I’ve found myself doing recently is ((x >> 8) as u8 & MASK)
, where x: u32
and MASK: u8
. The casts feel unnecessary and are an annoying papercut when bit-twiddling; after all, it is always the case that &
-ing any unsigned integer with a u8
will result in a value that fits into a u8
. I have a few thoughts on how to alleviate this papercut, and wether it’s worth it or not.
One solution is to allow &
and |
with types of mismatched size.
Currently, all of the binary integral operators x op y
require that x
and y
be of the exact same type. In general, this is a good thing; I’m not a fan of surprise widening conversions. However, I question if this is strictly necessary for BitAnd
and BitOr
. In the situation of bit-twiddling, it feels like casting is an unecessary additional assertion that “yes, I want to zero extend this mask to or it with something” or “yes, I want to truncate so I can and what’s left with a mask”. Thus, I’d like to see impl
s like the following in core
:
impl BitAnd<u8> for u32 {
type Output = u8;
fn bitand(self, other: u8) -> u8 { (self as u8) & other }
}
impl BitAnd<u8> for u32 {
type Output = u32;
fn bitand(self, other: u8) -> u8 { self | (other as u32) }
}
That said, I’m not entirely comfortable with the idea with BitAnd
and BitOr
not having the same strict behavior as the other ops including BitXor
, for which I don’t feel a behavior like this is anywhere near as useful.
An alternative, and somewhat more radical alternative is to create an “literal integral/float” pseudotype for constants, which we already know as {integer}
and {float}
in compiler errors. This imitates is how Go and C (via #define
) do constants: they are untyped until used. I consider this a failing of both languages, since untyped literals can cause a lot of strange errors when combined with inference, though I can see where such a thing could be useful.
Currently, const
s require a fixed type, which can be casted from, but I think being able to refrain typing const
s on an opt-out basis, e.g. const MASK: ulit = 0b0101_0101;
, could make the above impl
s unecessary (except for the automatic narrowing done by &
, which I don’t feel is fully justifiable anyways). I’m not sure what the semantics of such a pseudotype would be; in what situations are they allowed to be used as an expression? Do they still type as i32/u32/f64
if no type is inferred?
This is not so much a proposal, but I’d like to see what thoughts are on this casting papercut.