Rust has binary literals, a binary formatter, and a host of integer types, but no explicit binary numeric type.
'Almost-binary' integers
It's true that the expected implementation of an unsigned integer is a big/little-endian binary number in general purpose machines. However, that's far removed from the syntax of a high-level language. For example, if I have an eight-bit binary number 0000 0101 that I want to treat syntactically like a primitive numeric type, I have two problems: (1) the character representation of the number, and (2) the type declaration for the number. If I decide to stick with u8, I have to add a layer of string operations (in Rust) or a layer of vector operations (in MATLAB, for example) where numbers will be displayed or declared literally, and I have to ensure that the binary representation is converted to its equivalent in u8. In this situation, there's no way to make the direct statement 0000 0101 + 0000 0111 without this machinery bubbling up to the syntactic level, and that's just for binary types whose sizes happen to line up with integer types.
A 'true' binary type
An example would be, say, a hypothetical type b3, which is a 3-bit binary number, supporting the appropriate mathematical operations in its field. At a minimum, those operations would be arithmetic, closed over the type b3, of course. (The one defining the type would have to define a convention for how that closure is achieved in practice, e.g., by wrapping or asserting that the result of an operation that can't be expressed in b3 is not defined.)
A binary type like this could be declared as such and then used syntactically the same way as any other numeric type. Thus, 101 + 001 == 110, without the need to deploy bitwise operators, among other added requirements.
Under the hood
If these operations seem prosaic in a programming language that is already expected to have binary representations at its foundation, note that there are subtleties in implementing finite field arithmetic in C-like languages:
/* Multiply two numbers in the GF(2^8) finite field defined
* by the polynomial x^8 + x^4 + x^3 + x + 1 = 0
* using the Russian Peasant Multiplication algorithm
* (the other way being to do carry-less multiplication followed by a modular reduction)
*/
uint8_t gmul(uint8_t a, uint8_t b) {
uint8_t p = 0; /* the product of the multiplication */
while (b) {
if (b & 1) /* if b is odd, then add the corresponding a to p (final product = sum of all a's corresponding to odd b's) */
p ^= a; /* since we're in GF(2^m), addition is an XOR */
if (a & 0x80) /* GF modulo: if a >= 128, then it will overflow when shifted left, so reduce */
a = (a << 1) ^ 0x11b; /* XOR with the primitive polynomial x^8 + x^4 + x^3 + x + 1 (0b1_0001_1011) – you can change it but it must be irreducible */
else
a <<= 1; /* equivalent to a*2 */
b >>= 1; /* equivalent to b // 2 */
}
return p;
}
Why bother?
A Rust type with trait implementations accomplishing the above would collapse all of that down to Mul for b8, which seems to me to be a great feature about Rust. Being able to refer to characteristics of a b8 number using a more formal and standard interface than bitmasks and shifts would also seem to be a useful thing Rust could offer here.
What are the reasons why no such types are present anywhere in the core or crates?
u8is a binary numeric type! It's a number, it's represented as binary, you can do bitwise manipulation on it, you can declare it with a specific bit pattern. The problem is that you've mentioned all of that already, but are asking about something else that you haven't defined for us! Give us an example of another language handling it in the way you want, give us something, anything. - Shepmasterb8andu8? - Matthieu M.