nohash
A pass-through Rust Hasher that turns integer HashMap and HashSet lookups into a single stored value instead of a hash computation.
Repository Health
Technical Analysis
nohash is a minimal Rust crate implementing std::hash::Hasher for keys that are already integers, skipping the actual hashing step entirely. Instead of running an integer through a general-purpose algorithm like SipHash, NoHashHasher<T> simply stores the value passed to one write_* call and returns it unchanged from finish(), turning HashMap/HashSet lookups keyed by small integers into near no-op memory operations.
The crate ships IntMap<K, V> and IntSet<T> type aliases that wrap std::collections::HashMap/HashSet with BuildNoHashHasher pre-wired, plus the IsEnabled marker trait so custom types (like newtype wrappers around integer IDs) can opt in as long as their Hash implementation writes exactly one primitive value. In debug builds, the hasher asserts that assumption is upheld, panicking if write_* is called more than once — providing a safety net during development without any runtime cost in release builds. The crate is no_std-compatible for embedded and constrained environments, gated behind the default std feature flag.
What You Get
NoHashHasher<T>- aHasherimplementation that stores the raw integer passed towrite_u8/write_u32/etc. and returns it unchanged fromfinish()IntMap<K, V>andIntSet<T>aliases -HashMap/HashSettype aliases pre-configured withBuildNoHashHasher, ready to use as drop-in replacementsIsEnabledmarker trait - lets custom integer-backed types (IDs, newtypes) opt intoNoHashHasheronce theirHashimpl is verified to write exactly one value- Debug-mode misuse assertions - panics in debug builds if a hasher instance receives more than one
write_*call, catching incorrectHashimplementations early no_stdsupport - builds without the standard library when the defaultstdfeature is disabled, for embedded and constrained targets
Common Use Cases
- Hot-path integer-keyed maps - a game engine or simulation storing per-entity state in a
HashMap<EntityId, Component>skips SipHash entirely on every lookup - Interned/ID-based caches - compilers and interpreters that already use dense integer IDs for symbols or nodes avoid re-hashing IDs that are already well distributed
- Custom newtype keys - a project can wrap a
u64in a newtype, implementIsEnabled, and get the same no-hash speedup without exposing raw integers throughout the codebase no_stdembedded systems - firmware or kernel code needing an integer-keyed hash map without pulling in a full general-purpose hashing algorithm
Under The Hood
Architecture nohash is a single flat module (src/lib.rs, no submodules) that defines one generic struct, NoHashHasher<T>, in two mutually exclusive forms selected by #[cfg(debug_assertions)]: a debug build carries an extra bool flag used to assert that write_* is invoked exactly once, while a release build drops that field entirely for zero runtime overhead. Two Hasher trait implementations mirror this split, and a PhantomData<T> marker ties each hasher instance to the key type it’s meant to be used with without storing any actual T value. The public surface is completed by the IsEnabled marker trait, which types opt into to signal their Hash implementation writes exactly one primitive, and the IntMap<K, V>/IntSet<T> type aliases, which wrap std::collections::HashMap/HashSet with BuildHasherDefault<NoHashHasher<K>> so callers never construct the hasher directly. Because nothing downstream depends on NoHashHasher’s internal representation — only its Hasher/Default/Clone/Copy trait implementations are consumed — changing the core struct’s storage would only ripple into the two impl blocks and the type aliases in the same file.
Tech Stack The crate has zero external dependencies — Cargo.toml declares no [dependencies] entries at all, relying purely on core::hash::{BuildHasherDefault, Hasher} and, behind the default std feature flag, std::collections::{HashMap, HashSet}. Disabling the std feature compiles the crate under #![cfg_attr(not(feature = “std”), no_std)], dropping the IntMap/IntSet aliases but keeping NoHashHasher and IsEnabled usable in embedded or kernel contexts. It targets the 2018 edition, is dual-licensed Apache-2.0 OR MIT, and ships no build scripts, macros, or proc-macro dependencies. CI (.github/workflows/rust.yml) runs cargo build and cargo test on GitHub Actions for pushes and PRs against master, with no separate lint or formatting job.
Code Quality Tests live inline in a #[cfg(test)] mod tests block at the bottom of lib.rs: a single ok test exercises all ten supported primitive types’ write_* → finish() round trip, and ten separate #[should_panic] tests (one per type, gated on debug_assertions) verify the double-write guard fires correctly. Every public item also carries a doc comment with a runnable example, which cargo test executes as doctests, giving the crate meaningful coverage of its narrow surface despite having no dedicated tests/ integration directory. There’s no clippy or rustfmt configuration checked in, and CI doesn’t run either, so style and lint enforcement rely on contributor discipline rather than automation. Error handling is minimal by design — the write(&mut self, _: &[u8]) fallback simply panics, encoding the crate’s core invariant (exactly one primitive write per hash) as a hard failure rather than a Result, which is an intentional and idiomatic choice for a Hasher implementation.
API Design The public API is deliberately tiny: one generic struct, one marker trait, and two pre-wired collection aliases, so adopting it is typically a one-line swap from HashMap<K, V> to IntMap<K, V> with IntMap::default() replacing HashMap::new(). Naming follows std::hash conventions closely (BuildNoHashHasher mirrors BuildHasherDefault, IsEnabled reads like a capability marker trait), and every public item’s doc comment includes a compiling usage example, so there’s no separate guide or example directory to hunt through. The debug-mode assertion is a notable DX touch — it turns a hard-to-diagnose logic bug (a Hash impl that writes more than one primitive) into an immediate panic during development while costing nothing in release builds. The design isn’t novel — trivial/no-op hashers for pre-hashed or already-integer keys are a known pattern in the Rust ecosystem, alongside crates like rustc-hash and fxhash (though those hash rather than pass through) — but the execution here is clean, minimal, and well-documented for its narrow purpose.