High-performance HashMap and HashSet collections for Rust.
Powered by hashbrown (SwissTable layout) · Fueled by axhash (AES-NI accelerated hashing)
std::collections::HashMap uses SipHash-1-3 by default — a secure but comparatively slow hash function. For most workloads you don't need cryptographic resistance; you need throughput.
axhash-map swaps the hasher for axhash, which exploits hardware AES instructions (AES-NI on x86-64, AES on ARMv8) to produce hashes at near-memory-bandwidth speed. The underlying table is hashbrown (SwissTable), the same implementation that backs std::collections::HashMap in Rust's standard library — so the table operations are identical; only the hashing step is faster.
| Crate | Description |
|---|---|
axhash |
High-performance hashing engine |
axhash-map |
Fast HashMap/HashSet powered by hashbrown |
axhash-indexmap |
Ordered maps with AxHash |
axhash-dashmap |
Concurrent DashMap powered by AxHash |
┌──────────────────────────────────────────────────────────┐
│ axhash-map │
│ │
│ Type aliases (Serde-compatible) │
│ HashMap<K, V> HashSet<T> │
│ │
│ Branded newtypes (ergonomic constructors) │
│ AxHashMap<K, V> AxHashSet<T> │
│ │ │ │
│ hashbrown::HashMap hashbrown::HashSet │
│ (SwissTable layout) │
│ │ │ │
│ BuildHasherDefault<AxHasher> │
│ (AES-NI accelerated hash engine) │
└──────────────────────────────────────────────────────────┘
This crate provides two ways to use the same fast hasher. Pick the one that fits your situation:
Plain type aliases over hashbrown with BuildHasherDefault<AxHasher> baked in.
Because there is no wrapper struct, Serde and other #[derive]-based crates work out of the box.
use axhash_map::HashMap; // or HashSet
// Works with serde::Serialize / Deserialize without any extra config.
let mut map: HashMap<&str, u32> = HashMap::default();
map.insert("fast", 1);A thin newtype wrapper that adds the familiar ::new() / ::with_capacity() constructors.
Every hashbrown method is accessible transparently via Deref.
use axhash_map::AxHashMap;
let mut map: AxHashMap<&str, u32> = AxHashMap::new();
map.insert("fast", 1);| Need | Use |
|---|---|
::new() / ::with_capacity() |
AxHashMap / AxHashSet |
Serde #[derive(Serialize, Deserialize)] |
HashMap / HashSet |
| Custom / seeded hasher | AxHashMap::with_hasher(AxBuildHasher::with_seed(s)) |
Raw hashbrown access |
RawHashMap / RawHashSet |
Measured on Apple Silicon (release build, N = 100,000 items).
| Scenario | AxHashMap | std HashMap | Speedup |
|---|---|---|---|
Insert — u64 keys |
379 µs | 1,032 µs | 2.7× |
Insert — String keys |
896 µs | 1,673 µs | 1.9× |
| Lookup — all hits | 200 µs | 748 µs | 3.7× |
| Lookup — 50% hit / 50% miss | 767 µs | 1,994 µs | 2.6× |
| Iteration (full scan) | 130 µs | 124 µs | ~equal |
Iteration performance is effectively identical because iteration does not invoke the hasher.
Run the benchmarks yourself:
cargo bench --bench map_comparison
# HTML reports → target/criterion/[dependencies]
axhash-map = "0.1"No feature flags required. AES acceleration is detected at runtime; a portable fallback is used automatically on CPUs without AES instructions.
use axhash_map::HashMap;
use core::hash::BuildHasherDefault;
use axhash_map::AxHasher;
// Construct via Default (zero-cost).
let mut map: HashMap<&str, u32> = HashMap::default();
map.insert("alice", 42);
map.insert("bob", 17);
assert_eq!(map["alice"], 42);
assert_eq!(map.len(), 2);use axhash_map::AxHashMap;
let mut scores: AxHashMap<&str, u32> = AxHashMap::new();
scores.insert("alice", 42);
scores.insert("bob", 17);
scores.insert("carol", 99);
// Index operator
assert_eq!(scores["alice"], 42);
// Safe lookup
assert_eq!(scores.get("bob"), Some(&17));use axhash_map::AxHashSet;
let mut seen: AxHashSet<u64> = AxHashSet::new();
seen.insert(1);
seen.insert(2);
seen.insert(2); // duplicate — ignored
assert_eq!(seen.len(), 2);
assert!(seen.contains(&1));
let a: AxHashSet<u32> = [1, 2, 3].into_iter().collect();
let b: AxHashSet<u32> = [2, 3, 4].into_iter().collect();
let union: AxHashSet<u32> = a.union(&b).copied().collect();
let intersection: AxHashSet<u32> = a.intersection(&b).copied().collect();
assert_eq!(union.len(), 4);
assert_eq!(intersection.len(), 2);use axhash_map::{AxHashMap, AxHashSet, AxBuildHasher};
// Default (zero seed)
let map: AxHashMap<String, i32> = AxHashMap::new();
let set: AxHashSet<String> = AxHashSet::new();
// Pre-allocate to avoid rehashing
let map = AxHashMap::<String, i32>::with_capacity(10_000);
let set = AxHashSet::<String>::with_capacity(10_000);
// Custom seed — use OS entropy for hash-flooding resistance
let seed: u64 = 0xdeadbeef_cafebabe;
let map: AxHashMap<String, i32, AxBuildHasher> =
AxHashMap::with_hasher(AxBuildHasher::with_seed(seed));
// Custom seed + pre-allocated capacity
let map: AxHashMap<String, i32, AxBuildHasher> =
AxHashMap::with_capacity_and_hasher(10_000, AxBuildHasher::with_seed(seed));use axhash_map::HashMap;
// Use hashbrown's built-in constructors directly on the alias.
let mut map: HashMap<String, i32> = HashMap::default();
let mut map = HashMap::<String, i32>::with_capacity(10_000);use axhash_map::{AxHashMap, AxHashSet};
// FromIterator for AxHashMap
let map: AxHashMap<&str, usize> = [("a", 1), ("b", 2), ("c", 3)]
.into_iter()
.collect();
// FromIterator for AxHashSet
let set: AxHashSet<i32> = [1, 2, 3, 2, 1].into_iter().collect(); // len == 3
// Extend
let mut map: AxHashMap<u32, u32> = AxHashMap::new();
map.extend([(1, 10), (2, 20)]);
map.extend([(3, 30), (4, 40)]);AxHashMap and AxHashSet implement Deref / DerefMut to the underlying
hashbrown::HashMap / hashbrown::HashSet, so every method — entry,
retain, drain, reserve, shrink_to_fit, and more — is directly
accessible without any extra imports.
use axhash_map::AxHashMap;
let mut map: AxHashMap<&str, u32> = AxHashMap::new();
map.entry("hits").and_modify(|n| *n += 1).or_insert(1);
map.entry("hits").and_modify(|n| *n += 1).or_insert(1);
assert_eq!(map["hits"], 2);
map.insert("temp", 0);
map.retain(|_, v| *v > 0);
assert!(!map.contains_key("temp"));The crate re-exports RawHashMap and RawHashSet (the bare hashbrown types)
and provides From conversions in both directions so you can cross the boundary
without a direct hashbrown dependency in your own Cargo.toml.
use core::hash::BuildHasherDefault;
use axhash_map::{AxHashMap, RawHashMap, AxHasher};
// Wrap a raw hashbrown map.
let raw: RawHashMap<&str, u32, BuildHasherDefault<AxHasher>> =
RawHashMap::with_hasher(BuildHasherDefault::default());
let wrapped: AxHashMap<&str, u32> = raw.into();
// Unwrap back to hashbrown.
let raw: RawHashMap<&str, u32, BuildHasherDefault<AxHasher>> = wrapped.into_inner();The default hasher uses a constant seed — the output is deterministic across runs. This is fine for most workloads.
If your map accepts keys from untrusted external input (e.g. HTTP request parameters) and you want to defend against hash-flooding attacks, supply a random seed derived from OS entropy:
use axhash_map::{AxHashMap, AxBuildHasher};
// In production, generate from `rand`, `getrandom`, or similar.
let seed: u64 = 0x1234_5678_9abc_def0;
let mut map: AxHashMap<String, String, AxBuildHasher> =
AxHashMap::with_hasher(AxBuildHasher::with_seed(seed));This crate has no feature flags. The AES hardware path is selected at runtime via CPUID — you always ship a single binary.
axhash-map
├── axhash-core (AxHasher + AxBuildHasher — AES hash engine)
└── hashbrown (SwissTable, no default features — ahash excluded)
MIT — see LICENSE.