ADR-248: u64 and u32 Join the Integer Types, and Integers Have Bit Operators

Status: Superseded by ADR-285 — its D1 is D3, D2 is D19, D3-D5 are D9-D11, D6 is D4 there Date: September 28, 2026 Target Version: Nikaia 0.0.250 Answers: ADR-048 D2’s u32, u64 and isize stay out until a program asks - a program asks Related: ADR-043 D4 and D7 (a checked conversion and truncating_), ADR-028 D5 (an entry exists because a program asked for it), ADR-176 (the table a comptime map crosses as), ADR-196 (Rust moved into Nikaia), Part I 2.2

1. Context

The program that asks. crates/nikaia/src/fixed.rs builds the table a comptime map crosses as (ADR-176): FNV-1a over a key’s bytes, in u64, and CHD’s displacements, in u32. Moving it into Nikaia by ADR-196’s route - the next piece after spelling, dsl and http1 - needs four things the language does not have:

A compiler written in Nikaia needs all four anyway - interning, tables and fingerprints are hashing - and so does any program that reads a wire format, a bitmap or a file header. That bit operations belong in the language was not in doubt; this record decides how they fit the rules it already has.

Why not the other way round, as Java does. A language can keep its integers signed and give the unsigned questions names: >>>, remainderUnsigned, compareUnsigned. ADR-048 D2 already weighed that and took the other side for the byte - “Java has no unsigned type and pays for it in every byte-handling program, where a masking dance undoes the sign at each step” - and the same sentence decides the word.

2. Decisions

D1 - u64 and u32 are integer types a program writes.

Beside i64, i32 and u8, with every rule those have: an arithmetic result that does not fit aborts at every build (Part I 2.2); wrapping_, saturating_ and truncating_ are their names for wrapping, stopping at the limit and keeping the low digits (ADR-043 D7); a conversion is written as and one that may not fit is checked (ADR-043 D4). No two of them mix silently: u64 + i64 is refused where both are known, as i32 + i64 is.

usize and isize stay out: a length is an i64 (ADR-048 D1), and nothing here asks for a machine-width type.

D2 - A literal may be as large as a u64 holds.

The widest number a program writes is 18446744073709551615. Whether it fits is asked where a type stands beside it, as it always was (NK1116): a literal above i64::MAX is a u64’s and nothing else’s, and one with no type beside it is refused against i64, with the help naming u64.

D3 - &, |, ^, << and >> are operators, and ! negates the bits.

Written as every neighbouring language writes them, on integers only, both sides of one type - a shift’s count excepted, which is any integer. ! on an integer flips every bit, as on a bool it flips the one. The compound forms &=, |=, ^=, <<= and >>= stand beside +=.

A name for each was the other way, as the wrapping operations have names rather than operators. It is not taken: those names say something the operator does not - this may overflow, and that is intended - where hash ^ byte says exactly what hash.bit_xor(byte) would, in the spelling every reader already has.

On a bool they are refused, with && and || named: a & that does not short-circuit is a second spelling of a thing the language says once.

D4 - Bit operators bind tighter than comparisons, and one beside a comparison is parenthesised.

The order is Rust’s and Go’s: * / %, then + -, then << >>, then &, ^, |, then the comparisons, then && and ||. C put & below ==, so that a & mask == 0 meant a & (mask == 0); this order means what the line reads.

And a bit operator written next to a comparison without parentheses is refused (NK1197), the parenthesised line handed over: (a & mask) == 0. The order above makes the line mean what it reads, and a reader who learned C reads it the other way; a parenthesis costs two characters and ends the question for both.

D5 - A shift loses the bits it moves out, and a shift by the width or more aborts.

x << 3 on a u64 moves three bits off the top, and that is what a shift is for - it is not an overflow. A count as large as the type is wide, or negative, is the program’s arithmetic gone wrong, and it aborts, which is Part I 2.2’s list already. >> on an unsigned type fills with zeros and on a signed one with the sign, which is what the type says.

D6 - text.bytes() is the text’s UTF-8, one u8 at a time.

A sequence, as chars() is: for b in key.bytes(). The bytes are the text’s own encoding, so a hash over them is the hash other languages compute over the same text.

3. Consequences

4. Considered and rejected

Named bit operations (bit_xor, shl): D3.

Unsigned questions on signed types (Java’s >>> and remainderUnsigned): §1, and ADR-048 D2 before it.

No parenthesis rule, since the precedence is already the one that reads right: D4 - the rule costs two characters and removes the one misreading that is famous.

5. What is built

Built at 0.0.250, with fixed.rs as the program that asked.

This site is open source. Improve this page.