I think that the type names in Rust are much better than the ones in C. I’d choose
unsigned int or
uint_32t any day. Is there any reason (other than tradition) to use those counter-intuitive hard-to-learn names?
I know that names like
long are architecture-specific, but doesn’t that make the actual problem even worse? C is said to be “portable assembly”, but in the assembly language you actually have to use the correct registers and sizes, which are defined by architecture, just as in C. Therefore I’m not sure how this actually helps us to produce better code (in terms of readability).
The only problem I could think of is about byte size. If byte isn’t eight bits, then using bit-numbers in type names wouldn’t be so clever. But then, why don’t we use some clear names that tell us exact multiples of byte that the datatype contains, like sb (single byte), db (double byte), qb (quad byte)?
So, why do we use architecture-specific type names in C, even when it is not necessary?
Because the C language is designed to be implementable on any platform, no matter what set of integer sizes it natively provides.
In fact, none of the
intN_t types are guaranteed to exist at all. Only an implementation which provides a two’s complement signed integer of exactly N bits (no padding bits allowed) will define the corresponding
intN_t type (see section 18.104.22.168 of the C11 standard).
char, int, long, etc types are all guaranteed to exist, and they are guaranteed to allow certain ranges of values (see section 22.214.171.124.1 of the C11 standard), but they might not be two’s complement, they might allow more values than just the required range, they might all be the same type in the underlying implementation, and they might even be implemented in software instead of hardware. In other words, the “normal” integral C types guarantee absolutely nothing except a range of values that they can be assigned. That is what makes them maximally portable.
Of course, in practice the main reason we’re still using
char, int, long, etc is because that’s all C had for a long time, that’s what everyone’s used to using, and the technical differences between
char, int8_t, uint8_t, int_least8_t, int_fast8_t and so on simply don’t matter to a great deal of code.
But if you do care about getting the optimal types, or you’re on one of the projects where these distinctions do matter, then it’s worth pointing out that the int_leastN_t and int_fastN_t types are guaranteed to exist on all conforming implementations (see sections 126.96.36.199 and 188.8.131.52 of the C11 standard), which means they have a huge portability advantage over the
As for Rust, what I’ve managed to gather from their documentation is this: Rust’s integer types have to exist in all conforming Rust implementations, the signed ones are required to use two’s complement, they have to have defined overflow behavior, and I can’t find any evidence of a “no padding bits” requirement. In other words, Rust integer types are not even close to a perfect match for any of the C11 integer types.