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 lock
  • get_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