axhash-indexmap
Insertion-ordered IndexMap and IndexSet collections powered by
indexmap (SwissTable + Vec layout) and fueled by axhash (AES-NI accelerated hashing).
Why axhash-indexmap?
indexmap::IndexMap defaults to SipHash-1-3 — a secure but comparatively
slow hasher. Swapping in axhash (which exploits hardware AES instructions)
cuts hashing overhead dramatically while preserving everything that makes
IndexMap unique: deterministic insertion-order iteration and O(1) positional
access via get_index.
Ecosystem
| 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-indexmap │
│ │
│ Type aliases (Serde-compatible) │
│ IndexMap<K, V> IndexSet<T> │
│ │
│ Branded newtypes (ergonomic constructors) │
│ AxIndexMap<K, V> AxIndexSet<T> │
│ │ │ │
│ indexmap::IndexMap indexmap::IndexSet │
│ (SwissTable lookup + Vec insertion-order slots) │
│ │ │ │
│ BuildHasherDefault<AxHasher> │
│ (AES-NI accelerated hash engine) │
└──────────────────────────────────────────────────────────┘
Two usage modes
This crate provides two ways to use the same fast hasher. Pick the one that fits your situation:
Mode 1 — Type alias (IndexMap / IndexSet)
Plain type aliases over indexmap with BuildHasherDefault<AxHasher> baked in.
Because there is no wrapper struct, Serde and other #[derive]-based crates work out of the box.
use IndexMap; // or IndexSet
// Works with serde::Serialize / Deserialize without any extra config.
let mut map: = default;
map.insert;
map.insert;
map.insert;
// Insertion order guaranteed.
let keys: = map.keys.copied.collect;
assert_eq!;
Mode 2 — Branded newtype (AxIndexMap / AxIndexSet)
A thin newtype wrapper that adds the familiar ::new() / ::with_capacity()
constructors (which indexmap 2.x only provides on the default-RandomState
form). Every indexmap method is accessible transparently via Deref.
use AxIndexMap;
let mut map: = new;
map.insert;
map.insert;
map.insert;
assert_eq!;
| Need | Use |
|---|---|
::new() / ::with_capacity() |
AxIndexMap / AxIndexSet |
Serde #[derive(Serialize, Deserialize)] |
IndexMap / IndexSet |
| Custom / seeded hasher | AxIndexMap::with_hasher(AxBuildHasher::with_seed(s)) |
Raw indexmap access |
RawIndexMap / RawIndexSet |
Benchmark Results
Measured on Apple Silicon (release build, N = 100 000 items).
| Scenario | AxIndexMap | IndexMap (SipHash) | Speedup |
|---|---|---|---|
Insert — u64 keys |
998 µs | 1 905 µs | 1.9× |
Insert — String keys |
1 875 µs | 2 258 µs | 1.2× |
| Lookup — all hits | 249 µs | 873 µs | 3.5× |
| Lookup — 50 % hit / 50 % miss | 783 µs | 2 188 µs | 2.8× |
| Iteration (full scan) | 19 µs | 20 µs | ~equal |
Positional access (get_index) |
59 µs | 60 µs | ~equal |
Iteration and
get_indexare draws — both are pureVecoperations that never call the hasher. The gains are concentrated entirely in the hashing step (insert + key lookup).
Run the benchmarks yourself:
# HTML reports → target/criterion/
Installation
[]
= "0.1"
No feature flags required. AES acceleration is detected at runtime; a portable fallback is used automatically on CPUs without AES instructions.
For no_std + alloc environments:
= { = "0.1", = false }
Constructors
Branded newtype constructors
⚠ indexmap quirk:
indexmap2.x definesIndexMap::new()andIndexMap::with_capacity()only onIndexMap<K, V>(the default-hasher form), not onIndexMap<K, V, S>with a customS.AxIndexMapexists precisely to solve this.
| Goal | Call |
|---|---|
| Empty map | AxIndexMap::new() or new_map::<K, V>() |
| Pre-allocated map | AxIndexMap::with_capacity(n) or map_with_capacity::<K, V>(n) |
| Empty set | AxIndexMap::new() or new_set::<T>() |
| Pre-allocated set | AxIndexSet::with_capacity(n) or set_with_capacity::<T>(n) |
| Default (zero seed) | AxIndexMap::default() |
| Custom seed | AxIndexMap::with_hasher(AxBuildHasher::with_seed(s)) |
| Custom seed + capacity | AxIndexMap::with_capacity_and_hasher(n, AxBuildHasher::with_seed(s)) |
Type alias constructors
The IndexMap / IndexSet type aliases are plain indexmap types — use their
built-in hashbrown-backed constructors directly:
use IndexMap;
let mut map: = default;
let mut map = with_capacity_and_hasher;
Quick start
IndexMap (branded newtype)
use ;
// Empty map with axhash engine.
let mut map: = new_map;
map.insert;
map.insert;
map.insert;
// Standard key lookup.
assert_eq!;
assert_eq!;
// Iteration order == insertion order — guaranteed.
let keys: = map.keys.copied.collect;
assert_eq!;
// Positional access — unique to IndexMap.
assert_eq!;
assert_eq!;
// Pre-allocated map.
let mut big: = map_with_capacity;
for i in 0..100_000u64
IndexMap (type alias)
use IndexMap;
// Construct and use exactly like indexmap::IndexMap — Serde-compatible.
let mut map: = default;
map.insert;
map.insert;
map.insert;
// Insertion order + positional access still work — it's the same IndexMap.
assert_eq!;
IndexSet
use ;
let mut set: = new_set;
set.insert;
set.insert;
set.insert;
set.insert; // duplicate — ignored, order preserved
// Insertion order preserved after dedup.
let order: = set.iter.copied.collect;
assert_eq!;
// Positional access.
assert_eq!;
// Collect from iterator — duplicates dropped, first occurrence wins.
let set: = .into_iter.collect;
assert_eq!;
Seeded construction
Supply a random seed to harden against hash-flooding attacks when keys come from untrusted input (e.g. HTTP parameters):
use ;
// In production: derive seed from OS entropy (rand, getrandom, etc.).
let seed: u64 = 0xdeadbeef_cafebabe;
let mut map: =
with_hasher;
map.insert;
assert_eq!;
// With capacity + custom seed.
let mut map: =
with_capacity_and_hasher;
Entry API
use new_map;
let mut word_count = ;
for word in
assert_eq!;
assert_eq!;
assert_eq!;
// Iteration order reflects first-insertion order of each key.
let order: = word_count.keys.copied.collect;
assert_eq!;
Collecting and extending
use ;
// FromIterator for maps.
let map: =
.into_iter
.collect;
// FromIterator for sets — duplicates dropped.
let set: = .into_iter.collect;
assert_eq!;
// Extend.
let mut map: = default;
map.extend;
map.extend;
assert_eq!;
Sorting
use new_map;
let mut map = ;
map.insert;
map.insert;
map.insert;
// Sort in-place by key.
map.sort_keys;
let keys: = map.keys.copied.collect;
assert_eq!;
// Sort by value.
map.sort_by;
Set operations
use ;
let a: = .into_iter.collect;
let b: = .into_iter.collect;
let union: = a.union.copied.collect;
let inter: = a.intersection.copied.collect;
let diff: = a.difference.copied.collect;
let sym_diff: = a.symmetric_difference.copied.collect;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
Interoperability with raw indexmap types
The crate re-exports RawIndexMap and RawIndexSet so you can interoperate
with the underlying indexmap types without a direct indexmap dependency.
AxIndexMap<K, V> converts to/from RawIndexMap<K, V, S> via From:
use BuildHasherDefault;
use ;
// Build a raw indexmap with BuildHasherDefault<AxHasher>.
let mut raw: =
with_hasher;
raw.insert;
// Convert into the branded newtype.
let ax: = raw.into;
assert_eq!;
// And back.
let raw: = ax.into;
assert_eq!;
no_std support
Disable the std feature to target no_std + alloc environments:
= { = "0.1", = false }
Pure no_std without heap allocation is not supported because IndexMap
requires dynamic memory.
Feature flags
| Flag | Default | Effect |
|---|---|---|
std |
✅ on | Links against std. Disable for no_std + alloc. |
Dependency footprint
axhash-indexmap
├── axhash-core (AxHasher + AxBuildHasher — AES hash engine, no std dependency)
└── indexmap (SwissTable + Vec, no default features)
axhash-map is not a dependency — AxHasher lives in axhash-core
directly. Adding axhash-map would pull in hashbrown for no benefit.
License
MIT — see LICENSE.