axhash-dashmap 0.1.1

Concurrent DashMap and DashSet powered by dashmap (multi-shard RwLock) and fueled by axhash (AES-NI accelerated hashing).
Documentation

axhash-dashmap

Concurrent DashMap and DashSet collections for Rust.

Powered by dashmap (multi-shard RwLock) · Fueled by axhash (AES-NI accelerated hashing)

Crates.io Docs.rs License: MIT


Why axhash-dashmap?

dashmap::DashMap defaults to RandomState — a secure but slower hasher that re-seeds on every process start. axhash-dashmap 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.

In a sharded concurrent map, every shard lookup begins with a hash to determine which shard to lock. A faster hasher therefore reduces latency at the point of highest contention.

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-dashmap                            │
│                                                                 │
│  Type aliases (Serde-compatible)                                │
│  DashMap<K, V>                    DashSet<T>                    │
│                                                                 │
│  Branded newtypes (ergonomic constructors)                      │
│  AxDashMap<K, V>                  AxDashSet<T>                  │
│         │                               │                       │
│  dashmap::DashMap          dashmap::DashSet                     │
│  ┌──────────┬─── ··· ───┬──────────┐                           │
│  │ shard 0  │  shard 1  │ shard N  │  ← parking_lot::RwLock    │
│  └──────────┴─── ··· ───┴──────────┘                           │
│         │                                                       │
│        BuildHasherDefault<AxHasher>                             │
│         (AES-NI — shard selection + bucket probing)             │
└─────────────────────────────────────────────────────────────────┘

Key points:

  • Only the hash step changes. All concurrency guarantees, shard-locking semantics, and deadlock-safety properties of dashmap are fully preserved.
  • Zero overhead. AxDashMap is a #[inline(always)] newtype; the compiler sees through it entirely.
  • AES is detected at runtime. A portable software fallback is used automatically on CPUs without AES instructions — you ship one binary.

Two usage modes

Mode 1 — Type alias (DashMap / DashSet)

Plain type aliases over dashmap with BuildHasherDefault<AxHasher> baked in. Because there is no wrapper struct, Serde and other #[derive]-based crates work out of the box.

use axhash_dashmap::{DashMap, AxHasher};
use core::hash::BuildHasherDefault;

// Serde-compatible — no custom wrapper type in the type hierarchy.
let map: DashMap<&str, u32> =
    dashmap::DashMap::with_hasher(BuildHasherDefault::<AxHasher>::default());
map.insert("hello", 42);

Mode 2 — Branded newtype (AxDashMap / AxDashSet)

A thin newtype wrapper that adds ::new() / ::with_capacity() / ::with_shard_amount() constructors (dashmap::DashMap::new() is only defined for RandomState — not for generic S). Every DashMap method is accessible transparently via Deref.

use axhash_dashmap::AxDashMap;

let map: AxDashMap<&str, u32> = AxDashMap::new();
map.insert("hello", 42);
assert_eq!(*map.get("hello").unwrap(), 42);
Need Use
::new() / ::with_capacity() / ::with_shard_amount() AxDashMap / AxDashSet
Serde #[derive(Serialize, Deserialize)] DashMap / DashSet
Custom / seeded hasher AxDashMap::with_hasher(AxBuildHasher::with_seed(s))
Raw dashmap access RawDashMap / RawDashSet

Installation

[dependencies]
axhash-dashmap = "0.1"

Optional features

# Enable serde::Serialize / Deserialize on DashMap / DashSet
axhash-dashmap = { version = "0.1", features = ["serde"] }

# Enable rayon parallel iterators
axhash-dashmap = { version = "0.1", features = ["rayon"] }

Quick start

AxDashMap

use axhash_dashmap::AxDashMap;

// Create — zero-overhead newtype wrapper.
let scores: AxDashMap<&str, u32> = AxDashMap::new();

scores.insert("alice", 42);
scores.insert("bob",   17);
scores.insert("carol", 99);

// Lookup (returns a Ref guard — no clone needed).
assert_eq!(*scores.get("alice").unwrap(), 42);

// Entry API — increment atomically within a shard lock.
scores.entry("alice").and_modify(|v| *v += 1);
assert_eq!(*scores.get("alice").unwrap(), 43);

// Concurrent iteration.
let total: u32 = scores.iter().map(|r| *r.value()).sum();
assert_eq!(total, 159);

// Bulk mutation without extra allocation.
scores.alter_all(|_, v| v * 2);
assert_eq!(*scores.get("alice").unwrap(), 86);

// Predicate-based removal.
scores.retain(|_, v| *v > 50);

AxDashSet

use axhash_dashmap::AxDashSet;

let seen: AxDashSet<u64> = AxDashSet::new();
seen.insert(1);
seen.insert(2);
seen.insert(2); // duplicate — silently ignored

assert_eq!(seen.len(), 2);
assert!(seen.contains(&1));

Concurrent usage with Arc

use std::sync::Arc;
use axhash_dashmap::AxDashMap;

let map: Arc<AxDashMap<u32, u32>> = Arc::new(AxDashMap::with_capacity(10_000));

let handles: Vec<_> = (0..8)
    .map(|t| {
        let m = Arc::clone(&map);
        std::thread::spawn(move || {
            for i in 0u32..125 {
                m.insert(t * 125 + i, i * i);
            }
        })
    })
    .collect();

for h in handles { h.join().unwrap(); }
assert_eq!(map.len(), 1_000);

Constructors

AxDashMap

Goal Call
Empty map, default shards AxDashMap::new()
Pre-allocated map AxDashMap::with_capacity(n)
Custom shard count AxDashMap::with_shard_amount(shards)
Capacity + shard count AxDashMap::with_capacity_and_shard_amount(n, shards)
Default (zero seed) AxDashMap::default()
Seeded hasher AxDashMap::with_hasher(AxBuildHasher::with_seed(s))
Seeded + capacity AxDashMap::with_capacity_and_hasher(n, AxBuildHasher::with_seed(s))
Seeded + shard count AxDashMap::with_hasher_and_shard_amount(AxBuildHasher::with_seed(s), shards)

Shard count tip: shard_amount must be a power of two. Default = logical CPUs × 4, rounded up to the next power of two. More shards → less contention under high thread counts. Fewer shards → lower memory overhead for small maps.

AxDashSet

Goal Call
Empty set, default shards AxDashSet::new()
Pre-allocated set AxDashSet::with_capacity(n)
Default (zero seed) AxDashSet::default()
Seeded hasher AxDashSet::with_hasher(AxBuildHasher::with_seed(s))
Seeded + capacity AxDashSet::with_capacity_and_hasher(n, AxBuildHasher::with_seed(s))

Seeded construction

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

use axhash_dashmap::{AxDashMap, AxBuildHasher};

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

let map: AxDashMap<String, u32, AxBuildHasher> =
    AxDashMap::with_hasher(AxBuildHasher::with_seed(seed));
map.insert("untrusted_key".to_string(), 1);

All dashmap methods are available

AxDashMap and AxDashSet implement Deref / DerefMut to the underlying dashmap::DashMap / dashmap::DashSet, so every method is directly accessible without extra imports — entry, iter, alter, alter_all, retain, into_read_only, shrink_to_fit, get_mut, and more.

use axhash_dashmap::AxDashMap;

let map: AxDashMap<&str, u32> = AxDashMap::new();
map.insert("hits", 0);

// entry — modify-or-insert within a single shard lock.
map.entry("hits").and_modify(|n| *n += 1);

// retain — predicate-based removal.
map.insert("temp", 0);
map.retain(|_, v| *v > 0);
assert!(!map.contains_key("temp"));

// into_read_only — zero-copy promotion to a read-only view.
let map: AxDashMap<&str, u32> = [("a", 1), ("b", 2)].into_iter().collect();
let ro = map.into_inner().into_read_only();
assert_eq!(*ro.get("a").unwrap(), 1);

Interoperability with raw dashmap types

The crate re-exports RawDashMap and RawDashSet and provides From conversions in both directions so you can cross the boundary without a direct dashmap dependency in your own Cargo.toml.

use core::hash::BuildHasherDefault;
use axhash_dashmap::{AxDashMap, RawDashMap, AxHasher};

// Wrap a raw dashmap.
let raw: RawDashMap<&str, u32, BuildHasherDefault<AxHasher>> =
    dashmap::DashMap::with_hasher(BuildHasherDefault::default());
raw.insert("x", 99);

let ax: AxDashMap<&str, u32> = raw.into();
assert_eq!(*ax.get("x").unwrap(), 99);

// Unwrap back to dashmap.
let raw: RawDashMap<&str, u32, BuildHasherDefault<AxHasher>> = ax.into();
assert_eq!(*raw.get("x").unwrap(), 99);

Feature flags

Flag Default Effect
serde ❌ off Enables serde::Serialize / Deserialize on DashMap / DashSet.
rayon ❌ off Enables rayon parallel iterators on DashMap / DashSet.

Dependency footprint

axhash-dashmap
├── axhash-core   (AxHasher + AxBuildHasher — AES hash engine)
└── dashmap       (multi-shard RwLock concurrent map, no default features)

License

MIT — see LICENSE.