u64 and u32 Join the Integer Types, and Integers Have Bit OperatorsStatus: 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
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:
hash ^= byte then a multiply, and
the table cuts the hash into three parts with >> 32 and >> 16;hash % buckets over a hash whose top bit is
set is a negative number in an i64, and so is the index it makes;i64::MAX: FNV’s offset basis is
0xcbf29ce484222325, and Part I 2.2 refuses any number too wide for an
i64;chars() is not that.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.
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.
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.
&, |, ^, << 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.
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.
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.
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.
i64, i32, u8, u64, u32 -
and the floats, and offers no machine-width one.fixed.rs’s FNV and CHD can be written in Nikaia as they are written in Rust
(§5).NK1197, NK1198 and NK1199 join the table of codes.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.
Built at 0.0.250, with fixed.rs as the program that asked.
u64 and u32 wherever a number type is named - an
annotation, as, a field, a Vec[(u32, u32)] - with the widenings that
cannot lose a digit written as as and the others checked, and wrapping_,
saturating_ and truncating_ in std.contracts. u64 + i64 is NK1199
where both sides are known, and so is i32 + i64, which reached rustc as
cannot add i64 to i32 before; the message names both types and the help
hands over the as.u64::MAX and refuses
one wider by name. Above i64::MAX it is written with a u64 suffix, and one
with nothing but i64 beside it is NK1116, the help naming u64. An
unannotated let whose constant an i32 does not hold is an i64 to the
checker as well as to the emitter (ADR-063 D1).+ and the
comparisons in Rust’s order, ! on an integer, and the five compound forms.
A bit operator beside a comparison is NK1197 with the parenthesised line
as the help; on a bool or anything not an integer it is NK1198, naming
&&, || or !=. The build-time evaluator computes them, a shift by the
width or more stopping the fold as it stops the program.text.bytes() (D6), on a String and a view, as a sequence of u8.fixed.rs is Nikaia: crates/nikaia-std/src/tools/fixed.nika, lowered by
nikaia lower-std and include!d as nikaia_std::tools::fixed; the
compiler’s fixed module keeps its Table and converts the order to
usize. crates/nikaia/tests/fixed_map.rs runs the lookup over both table
shapes against it. Writing it found four lowering defects, fixed in the same
package (a call that changes an element, a for over an element,
x == null, and a borrow of a lent read), and two that are recorded in
#176 and #177.crates/nikaia/tests/bits.rs runs FNV-1a over hello against the value it
has everywhere else, each operator once, the three refusals, the widest
literal, and the widened constant, at both settings of user_parallelism.