Thoughts on Integers (2023)

birdculture1 pts0 comments

Thoughts On Integers · Luna’s Blog<br>Thoughts On Integers<br>In this post I’ll lay out my views on integer types,<br>and how I’ve come to those views.<br>Hopefully you’ll disagree initially and be convinced by the end!<br>The status quo<br>Let’s begin with what I’d characterize as the status quo<br>in popular programming languages today.<br>But first, some caveats:<br>I won’t mention scripting languages throughout this article for obvious reasons.<br>Moreover, I’m leaving Java out of this section because<br>it has some weirdness surrounding integers<br>(int vs Integer and no distinct unsigned types).<br>We’ll start with a classic systems programming language, C.<br>In addition to the traditional C integer types<br>int, char, short, long, long long, etc,<br>C99 added fixed size types with names like int8_t, uint64_t, and so on.<br>On modern 64-bit platforms int is always signed and 32 bits wide.<br>Additionally, C has a confusing array of machine-dependent types<br>such as ptrdiff_t and size_t.<br>In general, subscripts and sizes are of type size_t,<br>but C’s implicit conversions mean<br>you’ll often see int or something else used instead.<br>When signed integers overflow in C, they invoke undefined behavior,<br>meaning the compiler assumes the overflow never occurs.<br>Overflow for unsigned integers, on the other hand, is defined to wrap.<br>Next up, C#, which I’d characterize as<br>a typical “application programming” language<br>that’s used when low level control isn’t necessary.<br>C#’s types are named similarly to C’s: int, short, long and byte.<br>int is 32 bits wide.<br>Unsigned versions are obtained by prefixing the type with u.<br>Subscripts and sizes use a plain int,<br>and by default integers wrap on overflow.<br>Go is a contemporary alternative to languages in the same domain as C#.<br>Here we see the beginning of a trend:<br>rather than using unintuitive C-style integer type names,<br>Go just appends the bitwidth of each type: int8, uint64, etc.<br>Another interesting difference crops up in the size of int and uint:<br>they match the size of an address on the target platform,<br>rather than being hardcoded to a particular width.<br>Again, sizes and subscripts use int.<br>Go defines integer overflow to wrap.<br>Swift is yet another recent language in this<br>“compiled statically-typed non-low-level commercial software” niche,<br>and its integer types are similar to Go in several ways.<br>Type names are identical to Go except for capitalization: Int8, UInt64, etc.<br>Int and UInt have the same sizing behavior as Go,<br>and subscripts & sizes both use Int.<br>Curiously, Swift defaults to panicking on integer overflow,<br>even in release builds.<br>Rust: a better approach<br>Did you notice a commonality in all the examples I listed above?<br>All those languages have some kind of default int type.<br>They require extra typing to use unsigned types instead of signed ones<br>and explicitly-sized types instead of the default-sized int.<br>I’d say this encourages the use of int over other types.<br>After all: would you rather stare at a screenful of ints or uint32_ts?<br>This goes beyond aesthetics, too.<br>It’s so easy to take the lazy way out and type int without thinking,<br>rather than scrutinize the use-case to identify the best type for the job.<br>Rust eliminates each of these points:<br>all integer types have an explicit size,<br>and all types are prefixed with<br>either i or u for signed and unsigned respectively.<br>There’s no bias towards a certain size or signedness.<br>You’re forced to consider the circumstances and make a judgement on<br>what type is most appropriate to the situation.<br>Maybe memory is conserved as integers are narrowed,<br>and bugs are prevented as<br>– to invoke a dogma oft-cited by the Rust community –<br>invalid values (negative numbers) become unrepresentable.<br>namesigned?size in bitsu8no8u16no16u32no32u64no64usizenoaddress-sizedi8yes8i16yes16i32yes32i64yes64isizeyesaddress-sizedOne thing you might notice about this table<br>is the presence of usize and isize.<br>usize is used for subscripts and sizes, just like size_t in C.<br>Contrary to C, Rust doesn’t have implicit integer casting,<br>so you really do have to use usize for your array indexes<br>(unless you want to drown in as-casts).<br>Using usize rather than a 32-bit int<br>means arrays can have more than two billion elements,<br>and using an unsigned type means negative array indexes can never occur.<br>Rust panics on overflow in debug builds to catch bugs,<br>but uses wrapping in release builds for performance.<br>Note the lack of UB on overflow.<br>There is a caveat to what I said about<br>Rust not being biased to a particular integer type:<br>fn demo() {<br>let x = 1; // x: i32<br>Rust defaults to i32<br>when it has no type information for an integer literal,<br>so there is an (albeit minor) bias to signed 32-bit integers.<br>In my experience of Rust, though, I found I used signed integers so rarely<br>that I’d almost advocate for making unsigned integers the default.<br>If Rust didn’t put unsigned and signed integers on equal footing<br>I’d likely have never reached this conclusion.<br>To me, all of these changes seem like obvious improvements.<br>A Rust programmer tries other...

integers types type integer rust unsigned

Related Articles