Skip to main content

Crate axhash_indexmap

Crate axhash_indexmap 

Source
Expand description

§axhash-indexmap

Insertion-ordered IndexMap and IndexSet collections powered by indexmap (SwissTable + Vec layout) and fueled by axhash (AES-NI accelerated hashing).

Crates.io Docs.rs License: MIT


§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

CrateDescription
axhashHigh-performance hashing engine
axhash-mapFast HashMap/HashSet powered by hashbrown
axhash-indexmapOrdered maps with AxHash
axhash-dashmapConcurrent 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 axhash_indexmap::IndexMap; // or IndexSet

// Works with serde::Serialize / Deserialize without any extra config.
let mut map: IndexMap<&str, u32> = IndexMap::default();
map.insert("insertion", 1);
map.insert("order",     2);
map.insert("preserved", 3);

// Insertion order guaranteed.
let keys: Vec<&str> = map.keys().copied().collect();
assert_eq!(keys, ["insertion", "order", "preserved"]);

§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 axhash_indexmap::AxIndexMap;

let mut map: AxIndexMap<&str, u32> = AxIndexMap::new();
map.insert("insertion", 1);
map.insert("order",     2);
map.insert("preserved", 3);

assert_eq!(map.get_index(0), Some((&"insertion", &1)));
NeedUse
::new() / ::with_capacity()AxIndexMap / AxIndexSet
Serde #[derive(Serialize, Deserialize)]IndexMap / IndexSet
Custom / seeded hasherAxIndexMap::with_hasher(AxBuildHasher::with_seed(s))
Raw indexmap accessRawIndexMap / RawIndexSet

§Benchmark Results

Measured on Apple Silicon (release build, N = 100 000 items).

ScenarioAxIndexMapIndexMap (SipHash)Speedup
Insert — u64 keys998 µs1 905 µs1.9×
Insert — String keys1 875 µs2 258 µs1.2×
Lookup — all hits249 µs873 µs3.5×
Lookup — 50 % hit / 50 % miss783 µs2 188 µs2.8×
Iteration (full scan)19 µs20 µs~equal
Positional access (get_index)59 µs60 µs~equal

Iteration and get_index are draws — both are pure Vec operations that never call the hasher. The gains are concentrated entirely in the hashing step (insert + key lookup).

Run the benchmarks yourself:

cargo bench --bench indexmap_comparison
# HTML reports → target/criterion/

§Installation

[dependencies]
axhash-indexmap = "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:

axhash-indexmap = { version = "0.1", default-features = false }

§Constructors

§Branded newtype constructors

⚠ indexmap quirk: indexmap 2.x defines IndexMap::new() and IndexMap::with_capacity() only on IndexMap<K, V> (the default-hasher form), not on IndexMap<K, V, S> with a custom S. AxIndexMap exists precisely to solve this.

GoalCall
Empty mapAxIndexMap::new() or new_map::<K, V>()
Pre-allocated mapAxIndexMap::with_capacity(n) or map_with_capacity::<K, V>(n)
Empty setAxIndexMap::new() or new_set::<T>()
Pre-allocated setAxIndexSet::with_capacity(n) or set_with_capacity::<T>(n)
Default (zero seed)AxIndexMap::default()
Custom seedAxIndexMap::with_hasher(AxBuildHasher::with_seed(s))
Custom seed + capacityAxIndexMap::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 axhash_indexmap::IndexMap;

let mut map: IndexMap<String, u32> = IndexMap::default();
let mut map = IndexMap::<String, u32>::with_capacity_and_hasher(
    1024,
    core::hash::BuildHasherDefault::default(),
);

§Quick start

§IndexMap (branded newtype)

use axhash_indexmap::{new_map, map_with_capacity, AxIndexMap};

// Empty map with axhash engine.
let mut map: AxIndexMap<&str, u32> = new_map();
map.insert("one",   1);
map.insert("two",   2);
map.insert("three", 3);

// Standard key lookup.
assert_eq!(map["one"], 1);
assert_eq!(map.get("two"), Some(&2));

// Iteration order == insertion order — guaranteed.
let keys: Vec<&str> = map.keys().copied().collect();
assert_eq!(keys, ["one", "two", "three"]);

// Positional access — unique to IndexMap.
assert_eq!(map.get_index(0), Some((&"one", &1)));
assert_eq!(map.get_index(2), Some((&"three", &3)));

// Pre-allocated map.
let mut big: AxIndexMap<u64, u64> = map_with_capacity(100_000);
for i in 0..100_000u64 {
    big.insert(i, i * i);
}

§IndexMap (type alias)

use axhash_indexmap::IndexMap;

// Construct and use exactly like indexmap::IndexMap — Serde-compatible.
let mut map: IndexMap<&str, u32> = IndexMap::default();
map.insert("one",   1);
map.insert("two",   2);
map.insert("three", 3);

// Insertion order + positional access still work — it's the same IndexMap.
assert_eq!(map.get_index(0), Some((&"one", &1)));

§IndexSet

use axhash_indexmap::{new_set, AxIndexSet};

let mut set: AxIndexSet<u32> = new_set();
set.insert(5);
set.insert(3);
set.insert(1);
set.insert(3); // duplicate — ignored, order preserved

// Insertion order preserved after dedup.
let order: Vec<u32> = set.iter().copied().collect();
assert_eq!(order, [5, 3, 1]);

// Positional access.
assert_eq!(set.get_index(0), Some(&5));

// Collect from iterator — duplicates dropped, first occurrence wins.
let set: AxIndexSet<u32> = [3, 1, 4, 1, 5].into_iter().collect();
assert_eq!(set.len(), 4);

§Seeded construction

Supply a random seed to harden against hash-flooding attacks when keys come from untrusted input (e.g. HTTP parameters):

use axhash_indexmap::{AxIndexMap, AxBuildHasher};

// In production: derive seed from OS entropy (rand, getrandom, etc.).
let seed: u64 = 0xdeadbeef_cafebabe;

let mut map: AxIndexMap<String, usize, AxBuildHasher> =
    AxIndexMap::with_hasher(AxBuildHasher::with_seed(seed));
map.insert("hello".to_string(), 1);
assert_eq!(map["hello"], 1);

// With capacity + custom seed.
let mut map: AxIndexMap<String, usize, AxBuildHasher> =
    AxIndexMap::with_capacity_and_hasher(1024, AxBuildHasher::with_seed(seed));

§Entry API

use axhash_indexmap::new_map;

let mut word_count = new_map::<&str, u32>();

for word in ["apple", "banana", "apple", "cherry", "banana", "apple"] {
    word_count
        .entry(word)
        .and_modify(|n| *n += 1)
        .or_insert(1);
}

assert_eq!(word_count["apple"],  3);
assert_eq!(word_count["banana"], 2);
assert_eq!(word_count["cherry"], 1);

// Iteration order reflects first-insertion order of each key.
let order: Vec<&str> = word_count.keys().copied().collect();
assert_eq!(order, ["apple", "banana", "cherry"]);

§Collecting and extending

use axhash_indexmap::{AxIndexMap, AxIndexSet};

// FromIterator for maps.
let map: AxIndexMap<&str, u32> = [("a", 1), ("b", 2), ("c", 3)]
    .into_iter()
    .collect();

// FromIterator for sets — duplicates dropped.
let set: AxIndexSet<i32> = [1, 2, 3, 2, 1].into_iter().collect();
assert_eq!(set.len(), 3);

// Extend.
let mut map: AxIndexMap<u32, u32> = AxIndexMap::default();
map.extend([(1, 10), (2, 20)]);
map.extend([(3, 30)]);
assert_eq!(map.len(), 3);

§Sorting

use axhash_indexmap::new_map;

let mut map = new_map::<&str, u32>();
map.insert("banana", 2);
map.insert("apple",  1);
map.insert("cherry", 3);

// Sort in-place by key.
map.sort_keys();
let keys: Vec<&str> = map.keys().copied().collect();
assert_eq!(keys, ["apple", "banana", "cherry"]);

// Sort by value.
map.sort_by(|_, a, _, b| a.cmp(b));

§Set operations

use axhash_indexmap::{new_set, AxIndexSet};

let a: AxIndexSet<u32> = [1, 2, 3, 4].into_iter().collect();
let b: AxIndexSet<u32> = [3, 4, 5, 6].into_iter().collect();

let union:     AxIndexSet<u32> = a.union(&b).copied().collect();
let inter:     AxIndexSet<u32> = a.intersection(&b).copied().collect();
let diff:      AxIndexSet<u32> = a.difference(&b).copied().collect();
let sym_diff:  AxIndexSet<u32> = a.symmetric_difference(&b).copied().collect();

assert_eq!(union.len(),    6);
assert_eq!(inter.len(),    2);
assert_eq!(diff.len(),     2);
assert_eq!(sym_diff.len(), 4);

§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 core::hash::BuildHasherDefault;
use axhash_indexmap::{AxIndexMap, RawIndexMap, AxHasher};

// Build a raw indexmap with BuildHasherDefault<AxHasher>.
let mut raw: RawIndexMap<&str, i32, BuildHasherDefault<AxHasher>> =
    RawIndexMap::with_hasher(BuildHasherDefault::default());
raw.insert("x", 99);

// Convert into the branded newtype.
let ax: AxIndexMap<&str, i32> = raw.into();
assert_eq!(ax["x"], 99);

// And back.
let raw: RawIndexMap<&str, i32, BuildHasherDefault<AxHasher>> = ax.into();
assert_eq!(raw["x"], 99);

§no_std support

Disable the std feature to target no_std + alloc environments:

axhash-indexmap = { version = "0.1", default-features = false }

Pure no_std without heap allocation is not supported because IndexMap requires dynamic memory.


§Feature flags

FlagDefaultEffect
std✅ onLinks 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.

Structs§

AxBuildHasher
The BuildHasher used for seeded / custom construction via AxIndexMap::with_hasher.
AxHasher
The BuildHasher used for seeded / custom construction via AxIndexMap::with_hasher.
AxIndexMap
AxIndexMap<K, V> is a thin newtype wrapper around IndexMap<K, V> that adds the familiar ::new() / ::with_capacity() constructor syntax (which indexmap 2.x only provides on the default-RandomState form). Every method on indexmap::IndexMap is accessible via Deref.
AxIndexSet
Insertion-ordered set backed by indexmap with AxHasher as the default hasher.
MapOccupiedEntry
A view into an occupied entry in an IndexMap. It is part of the Entry enum.
MapVacantEntry
A view into a vacant entry in an IndexMap. It is part of the Entry enum.
RawIndexMap
Raw indexmap::IndexMap — available without a direct indexmap dep. A hash table where the iteration order of the key-value pairs is independent of the hash values of the keys.
RawIndexSet
Raw indexmap::IndexSet — available without a direct indexmap dep. A hash set where the iteration order of the values is independent of their hash values.

Enums§

MapEntry
indexmap::map::Entry re-exported for ergonomic match arms. Entry for an existing key-value pair in an IndexMap or a vacant location to insert one.

Functions§

map_with_capacity
Creates an AxIndexMap pre-allocated to at least capacity entries.
new_map
Creates an empty AxIndexMap using the default AxHasher.
new_set
Creates an empty AxIndexSet using the default AxHasher.
set_with_capacity
Creates an AxIndexSet pre-allocated to at least capacity entries.

Type Aliases§

IndexMap
Drop-in indexmap::IndexMap with AxHasher as the default hasher. Insertion order is fully preserved. Use this alias when maximum third-party compatibility matters (e.g. Serde #[derive]).
IndexSet
Drop-in indexmap::IndexSet with AxHasher as the default hasher. Insertion order is fully preserved. Use this alias when maximum third-party compatibility matters (e.g. Serde #[derive]).