How do I work with DashMap for Concurrent Hash Maps in Rust?
Walkthrough
DashMap is a blazing fast concurrent map that provides thread-safe access without requiring explicit locking. It uses sharding (partitioning data across multiple inner HashMaps) to minimize lock contention, making it significantly faster than wrapping a HashMap in a Mutex or RwLock for concurrent access.
Key concepts:
- Sharding — Data split across multiple hash map shards
- Fine-grained locking — Only locks relevant shard
- O(1) operations — Constant time for most operations
- DashMap<K, V> — The main concurrent map type
- Ref/KVRef — Guards for borrowed references
When to use DashMap:
- High-concurrency scenarios
- Read-heavy workloads with occasional writes
- When you need thread-safe HashMap without Mutex overhead
- Web servers, caches, shared state
When NOT to use DashMap:
- Single-threaded applications (use HashMap)
- When you need atomic multi-key operations
- When memory overhead is critical
- When you need sorted iteration
Code Examples
Basic DashMap Usage
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let map = Arc::new(DashMap::new());
// Spawn multiple threads writing to the map
let mut handles = vec![];
for i in 0..5 {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
map.insert(i, format!("value-{}", i));
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Map contents: {:?}", map);
}Read Operations
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("apple", 3);
map.insert("banana", 2);
map.insert("cherry", 5);
// Get value (returns reference guard)
if let Some(value) = map.get(&"apple") {
println!("Apple: {}", *value);
}
// Check if key exists
println!("Contains 'banana': {}", map.contains_key(&"banana"));
// Get key-value pair
if let Some(entry) = map.get_key_value(&"cherry") {
println!("Key: {}, Value: {}", entry.0, entry.1);
}
}Mutable Access
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("counter", 0);
// Get mutable reference
if let Some(mut value) = map.get_mut(&"counter") {
*value += 1;
}
println!("Counter: {:?}", map.get(&"counter"));
}Entry API
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// Insert if not present
map.entry("apple").or_insert(5);
map.entry("apple").or_insert(10); // Won't overwrite
// Insert with default function
map.entry("banana").or_insert_with(|| {
println!("Creating default for banana");
3
});
// Modify existing entry
map.entry("apple").and_modify(|v| *v += 1);
println!("Map: {:?}", map);
}Remove Operations
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
// Remove by key
let removed = map.remove(&"b");
println!("Removed: {:?}", removed);
// Remove if matches condition
map.remove_if(&"c", |_, v| *v > 5);
println!("'c' still exists: {}", map.contains_key(&"c"));
// Remove entry
let entry = map.remove_entry(&"a");
println!("Removed entry: {:?}", entry);
}Iteration
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
// Iterate over entries (locks each shard as needed)
for entry in map.iter() {
println!("{} = {}", entry.key(), entry.value());
}
// Iterate over keys
for key in map.iter().map(|e| e.key().clone()).collect::<Vec<_>>() {
println!("Key: {}", key);
}
}With Shard Amount
use dashmap::DashMap;
fn main() {
// Create with specific number of shards (must be power of 2)
let map: DashMap<i32, String> = DashMap::with_shard_amount(16);
// More shards = less contention, more memory overhead
println!("Shards: {}", map.shards().len());
}Capacity Management
use dashmap::DashMap;
fn main() {
// Create with capacity
let map: DashMap<i32, String> = DashMap::with_capacity(100);
println!("Capacity: {}", map.capacity());
println!("Len: {}", map.len());
for i in 0..50 {
map.insert(i, format!("value-{}", i));
}
println!("After inserts - Len: {}, Capacity: {}", map.len(), map.capacity());
// Shrink to fit
map.shrink_to_fit();
println!("After shrink - Capacity: {}", map.capacity());
}Clear and Is Empty
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
println!("Is empty: {}", map.is_empty());
println!("Len: {}", map.len());
map.clear();
println!("After clear - Is empty: {}", map.is_empty());
}Concurrent Counter Pattern
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let counters = Arc::new(DashMap::new());
let mut handles = vec![];
// Spawn threads that increment counters
for i in 0..10 {
let counters = Arc::clone(&counters);
handles.push(thread::spawn(move || {
for j in 0..100 {
let key = format!("counter-{}", j % 3);
counters.entry(key).and_modify(|v| *v += 1).or_insert(1);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Final counts:");
for entry in counters.iter() {
println!(" {}: {}", entry.key(), entry.value());
}
}Web Request Cache Pattern
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
struct CachedResponse {
body: String,
cached_at: Instant,
}
struct RequestCache {
cache: DashMap<String, CachedResponse>,
ttl: Duration,
}
impl RequestCache {
fn new(ttl_secs: u64) -> Self {
Self {
cache: DashMap::new(),
ttl: Duration::from_secs(ttl_secs),
}
}
fn get(&self, url: &str) -> Option<String> {
self.cache.get(url).and_then(|entry| {
if entry.cached_at.elapsed() < self.ttl {
Some(entry.body.clone())
} else {
None
}
})
}
fn set(&self, url: String, body: String) {
self.cache.insert(url, CachedResponse {
body,
cached_at: Instant::now(),
});
}
fn invalidate(&self, url: &str) {
self.cache.remove(url);
}
}
fn main() {
let cache = Arc::new(RequestCache::new(60));
cache.set("https://example.com".to_string(), "Hello".to_string());
if let Some(body) = cache.get("https://example.com") {
println!("Cached: {}", body);
}
}Rate Limiting Pattern
use dashmap::DashMap;
use std::time::Instant;
struct RateLimiter {
requests: DashMap<String, (u32, Instant)>,
max_requests: u32,
window_secs: u64,
}
impl RateLimiter {
fn new(max_requests: u32, window_secs: u64) -> Self {
Self {
requests: DashMap::new(),
max_requests,
window_secs,
}
}
fn check(&self, client_id: &str) -> bool {
let now = Instant::now();
let window = std::time::Duration::from_secs(self.window_secs);
self.requests.entry(client_id.to_string())
.and_modify(|(count, time)| {
if now.duration_since(*time) > window {
*count = 1;
*time = now;
} else {
*count += 1;
}
})
.or_insert((1, now));
self.requests.get(client_id)
.map(|entry| entry.0 <= self.max_requests)
.unwrap_or(true)
}
}
fn main() {
let limiter = RateLimiter::new(3, 60);
println!("Request 1: {}", limiter.check("client-1"));
println!("Request 2: {}", limiter.check("client-1"));
println!("Request 3: {}", limiter.check("client-1"));
println!("Request 4: {}", limiter.check("client-1")); // Should be false
}User Session Store
use dashmap::DashMap;
use std::sync::Arc;
#[derive(Clone)]
struct Session {
user_id: u64,
username: String,
created_at: std::time::Instant,
}
struct SessionStore {
sessions: DashMap<String, Session>,
}
impl SessionStore {
fn new() -> Self {
Self {
sessions: DashMap::new(),
}
}
fn create_session(&self, user_id: u64, username: String) -> String {
let session_id = format!("session-{}-{}", user_id, uuid::Uuid::new_v4());
self.sessions.insert(session_id.clone(), Session {
user_id,
username,
created_at: std::time::Instant::now(),
});
session_id
}
fn get_session(&self, session_id: &str) -> Option<Session> {
self.sessions.get(session_id).map(|s| s.clone())
}
fn remove_session(&self, session_id: &str) {
self.sessions.remove(session_id);
}
fn active_sessions(&self) -> usize {
self.sessions.len()
}
}
fn main() {
let store = SessionStore::new();
let session_id = store.create_session(123, "alice".to_string());
println!("Created session: {}", session_id);
if let Some(session) = store.get_session(&session_id) {
println!("User: {}", session.username);
}
store.remove_session(&session_id);
println!("Active sessions: {}", store.active_sessions());
}Shard Inspection
use dashmap::DashMap;
fn main() {
let map: DashMap<i32, String> = DashMap::with_shard_amount(4);
for i in 0..20 {
map.insert(i, format!("value-{}", i));
}
// Check number of shards
println!("Number of shards: {}", map.shards().len());
// Calculate entries per shard
let mut shard_counts = vec![0; map.shards().len()];
for (i, shard) in map.shards().iter().enumerate() {
shard_counts[i] = shard.read().len();
}
println!("Entries per shard: {:?}", shard_counts);
}Retain and Filter
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
map.insert("d", 4);
// Retain only even values
map.retain(|_k, v| *v % 2 == 0);
println!("After retain:");
for entry in map.iter() {
println!(" {} = {}", entry.key(), entry.value());
}
}Compare and Swap
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("counter", 0);
// Compare and update
let updated = map.update(&"counter", |v| *v + 1);
println!("Updated: {:?}", updated);
// Conditional update
map.view(&"counter", |k, v| {
if *v < 10 {
println!("Counter {} is less than 10", k);
}
});
println!("Final value: {:?}", map.get(&"counter"));
}Multiple Values Per Key
use dashmap::DashMap;
use std::collections::HashSet;
fn main() {
// Store multiple values per key
let map: DashMap<String, HashSet<i32>> = DashMap::new();
map.entry("group-a".to_string())
.or_insert_with(HashSet::new)
.insert(1);
map.entry("group-a".to_string())
.and_modify(|set| { set.insert(2); });
if let Some(set) = map.get(&"group-a".to_string()) {
println!("Group A: {:?}", *set);
}
}DashMap vs Mutex
use dashmap::DashMap;
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use std::thread;
use std::time::Instant;
fn main() {
const OPERATIONS: usize = 100_000;
const THREADS: usize = 8;
// DashMap benchmark
let dash_map = Arc::new(DashMap::new());
let start = Instant::now();
let mut handles = vec![];
for _ in 0..THREADS {
let map = Arc::clone(&dash_map);
handles.push(thread::spawn(move || {
for i in 0..OPERATIONS / THREADS {
map.insert(i, i);
let _ = map.get(&i);
}
}));
}
for h in handles {
h.join().unwrap();
}
println!("DashMap: {:?}", start.elapsed());
// Mutex<HashMap> benchmark
let mutex_map = Arc::new(Mutex::new(HashMap::new()));
let start = Instant::now();
let mut handles = vec![];
for _ in 0..THREADS {
let map = Arc::clone(&mutex_map);
handles.push(thread::spawn(move || {
for i in 0..OPERATIONS / THREADS {
map.lock().unwrap().insert(i, i);
let _ = map.lock().unwrap().get(&i);
}
}));
}
for h in handles {
h.join().unwrap();
}
println!("Mutex<HashMap>: {:?}", start.elapsed());
}Atomic Batch Operations
use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// Batch insert
let items = vec![("a", 1), ("b", 2), ("c", 3)];
for (k, v) in items {
map.insert(k, v);
}
// Batch remove
let keys = vec!["a", "b"];
for k in keys {
map.remove(k);
}
println!("Remaining: {:?}", map.iter().collect::<Vec<_>>());
}Summary
DashMap Type Signature:
use dashmap::DashMap;
let map: DashMap<K, V> = DashMap::new();Key Methods:
| Method | Description |
|---|---|
insert(k, v) |
Insert key-value pair |
get(&k) |
Get value (returns guard) |
get_mut(&k) |
Get mutable value |
contains_key(&k) |
Check if key exists |
remove(&k) |
Remove by key |
entry(k) |
Entry API |
iter() |
Iterate over entries |
len() |
Number of entries |
is_empty() |
Check if empty |
clear() |
Remove all entries |
retain(f) |
Filter entries |
shards() |
Access inner shards |
DashMap vs Other Concurrent Maps:
| Feature | DashMap | Mutex | RwLock |
|---|---|---|---|
| Read parallelism | Yes | No | Yes |
| Write parallelism | Yes | No | No |
| Lock granularity | Per-shard | Global | Global |
| Memory overhead | Higher | Lower | Lower |
| Contention | Low | High | Medium |
Time Complexity:
| Operation | Complexity |
|---|---|
| Insert | O(1) |
| Get | O(1) |
| Remove | O(1) |
| Iterate | O(n) |
Key Points:
- Uses sharding for fine-grained locking
- No need for explicit Mutex or RwLock
get()returns a guard that holds a read lockget_mut()returns a guard that holds a write lock- Entry API similar to HashMap
- Choose shard count based on expected concurrency
- Higher memory overhead than plain HashMap
- Perfect for high-concurrency scenarios
- Great for web servers, caches, and shared state
