HashRapidV3Nano
HashRapidV3Nano hashes bytes to a 64-bit value that never changes. Write it into a cache header, a save file or a network message: a different build of your program, on a different machine, years later, computes the same value from the same bytes.
const u64 key = HashRapidV3Nano(file_bytes);WriteCacheHeader(out, key); // safe: the value is part of your file format nowThe name is the contract. HashRapidV3Nano is rapidhash V3, Nano variant, unseeded — a published algorithm anyone can compile to reproduce every value — and it is frozen for the life of the framework: it is never upgraded, only ever joined by new stable hashes under their own names.
Two hashes, two contracts
Section titled “Two hashes, two contracts”Basalt ships two kinds of hash, split on one axis: what happens to the value after the process ends.
HashFast |
HashRapidV3Nano |
|
|---|---|---|
| The value is valid | within one process of one build | forever, everywhere |
| Persist or transmit it | never | the intended use |
| The algorithm may change | in any version | never |
| Use it for | table keys, in-memory caches | file formats, disk caches, wire protocols |
Today HashFast forwards to HashRapidV3Nano, so the two return the same value for the same bytes and cost the same. The contracts still differ: HashFast is free to forward elsewhere in a future version, and only the value you obtained under the stable name is a value you may keep. Choosing stability is spelling the algorithm at the call site — nothing more.
Stable hashes are named by their full algorithmic identity — algorithm, version, variant — because the identity is what the promise pins. A better algorithm never replaces one; it joins the collection under its own name, the way XXH32, XXH64 and XXH3 coexist, each frozen. Code written against HashRapidV3Nano keeps working, and keeps reading its old files, no matter what ships later.
What the promise rests on
Section titled “What the promise rests on”A frozen value is only as trustworthy as the thing that freezes it. Three mechanisms carry it:
- The algorithm is published, not local. rapidhash V3 by Nicolas De Carli,
rapidhashNanovariant, called unseeded. You can compile the reference yourself and reproduce every value Basalt ever emitted — the promise is checkable from outside. - 325 frozen reference vectors pin it in the build. The corpus crosses every branch inside the algorithm — every length 0–200, every alignment offset, degenerate contents, long inputs. For this function the table is a public contract and is never regenerated: any change to any output bit fails the build, and the only fix is to revert the change.
- The output is a function of the bytes alone. Not of the buffer’s address, its alignment, the compiler, the optimisation level, or the machine’s byte order. The reads are defined little-endian, so x86-64 and ARM64 agree by construction.
What it does not promise
Section titled “What it does not promise”Reproducing someone else’s seeded value
Section titled “Reproducing someone else’s seeded value”HashRapidV3Nano(bytes, seed) gives you the same thing the reference’s rapidhashNano_withSeed gives, frozen as a function of both arguments. It is there for one job: reproducing a value that some other system — a format, a tool, a service — computed under a seed of its choosing. HashRapidV3Nano(bytes) is exactly HashRapidV3Nano(bytes, 0), so if nothing is forcing a seed on you, use the shorter call.
Hashing values, not bytes
Section titled “Hashing values, not bytes”There is deliberately no HashRapidV3Nano(u64) and no overload set. A persisted hash of a struct or an integer depends on how you serialised it — width, byte order, padding — and that layout is part of your file format, not of a hash function. Serialise to bytes first, then hash exactly the bytes you persist:
u8 header[12];WriteLittleEndianU32(header + 0, version);WriteLittleEndianU64(header + 4, asset_id);
const u64 key = HashRapidV3Nano(Str8(header, sizeof(header)));Hashing a struct’s memory directly (Str8(reinterpret_cast<const u8*>(&s), sizeof(s))) breaks the promise from your side: padding bytes are indeterminate and field layout can change between compilers — the hash stays frozen, but the bytes you feed it do not.
When this is the wrong tool
Section titled “When this is the wrong tool”- In-memory table keys. Use
HashFast: same speed today, and it keeps the framework free to make your tables faster later. Pinning the stable name where nothing is persisted buys you nothing and costs that freedom. - Content at volume — a kilobyte and up. Files, assets and large buffers belong to
HashRapidV3, the full function, which overtakes this one at about 1 KiB and is 1.2–1.4× faster at 4 KiB. Below that, including at a few hundred bytes, this one is still the faster of the two — and at 48 bytes and below the two give the same value, so nothing is lost by starting here. - Untrusted content. Anything an adversary can craft — see the danger note above.
- Content too large to hold in memory at once. There is no streaming interface; the promise is over one byte string viewed whole. A file too large for one
Spanneeds a different tool.
API summary
Section titled “API summary”Parameters, assertions and the full contract are documented in basalt/core/hash/HashRapidV3Nano.h.
| Signature | Purpose |
|---|---|
HashRapidV3Nano(String8 bytes) |
The frozen 64-bit hash of the bytes. rapidhash V3, Nano variant, unseeded. |
HashRapidV3Nano(String8 bytes, u64 seed) |
The same under an explicit seed; seed == 0 is the unseeded value. |