How do I use a concurrent HashMap with dashmap in Rust?

Walkthrough

The dashmap crate provides a blazingly fast concurrent HashMap for Rust. It uses sharding to achieve high performance under concurrent access, avoiding the bottleneck of a single lock. Instead of one lock for the entire map, DashMap divides the data into multiple shards, each with its own lock. This allows multiple threads to access different parts of the map simultaneously. It implements the standard HashMap API with additional methods for atomic operations like entry, get_or_insert, and compute_if_absent. DashMap is ideal for high-throughput concurrent applications like web servers, caches, and real-time data processing.

Key concepts:

  1. Sharding — data divided into multiple independent shards for parallel access
  2. Lock-free reads — multiple readers can access different shards simultaneously
  3. Atomic operations — atomic updates without holding locks manually
  4. Entry API — similar to HashMap's entry API for conditional operations
  5. Reference guards — returned references use RAII guards for safe access

Code Example

# Cargo.toml
[dependencies]
dashmap = "5"
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
 
fn main() {
    // Create a concurrent HashMap
    let map = Arc::new(DashMap::new());
    
    // Spawn multiple threads
    let handles: Vec<_> = (0..4)
        .map(|i| {
            let map = Arc::clone(&map);
            thread::spawn(move || {
                // Each thread can safely insert
                map.insert(i, format!("value-{}", i));
                
                // And read
                if let Some(value) = map.get(&i) {
                    println!("Thread {} read: {}", i, value.value());
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Final map size: {}", map.len());
}

Basic Operations

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // Insert
    map.insert("apple", 1);
    map.insert("banana", 2);
    map.insert("cherry", 3);
    
    println!("Size: {}", map.len());
    
    // Get
    if let Some(value) = map.get(&"apple") {
        println!("apple: {}", value.value());
    }
    
    // Contains
    println!("Contains 'banana': {}", map.contains_key(&"banana"));
    
    // Remove
    let removed = map.remove(&"banana");
    println!("Removed: {:?}", removed);
    
    println!("Size after removal: {}", map.len());
    
    // Clear
    map.clear();
    println!("Size after clear: {}", map.len());
}

Creating DashMap with Capacity

use dashmap::DashMap;
 
fn main() {
    // Create with initial capacity
    let map: DashMap<i32, String> = DashMap::with_capacity(100);
    
    // Create with shard amount (must be power of 2)
    let map: DashMap<i32, String> = DashMap::with_shard_amount(16);
    
    // Create with both
    let map: DashMap<i32, String> = DashMap::with_capacity_and_shard_amount(1000, 32);
    
    println!("Map created with {} shards", map.shards().len());
}

Reading Values

use dashmap::DashMap;
 
fn main() {
    let map = DashMap::new();
    map.insert("key1", "value1");
    map.insert("key2", "value2");
    
    // Get returns a reference guard
    if let Some(refr) = map.get(&"key1") {
        println!("Got key1: {}", refr.value());
        println!("Key: {}, Value: {}", refr.key(), refr.value());
    }
    
    // Get key-value pair
    if let Some((key, value)) = map.get_key_value(&"key2") {
        println!("Key: {}, Value: {}", key, value);
    }
    
    // Try get (returns Option<Result>)
    match map.try_get(&"key1") {
        Some(result) => {
            match result {
                Ok(guard) => println!("Got: {}", guard.value()),
                Err(_) => println!("Would block"),
            }
        }
        None => println!("Key not found"),
    }
}

Mutable Access

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = 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"));
    
    // Try get mutable
    match map.try_get_mut(&"counter") {
        Some(result) => {
            match result {
                Ok(mut guard) => *guard += 10,
                Err(_) => println!("Would block"),
            }
        }
        None => println!("Key not found"),
    }
    
    println!("Counter: {:?}", map.get(&"counter"));
}

Entry API

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // or_insert - insert if missing
    map.entry("apple").or_insert(5);
    println!("apple: {:?}", map.get(&"apple"));
    
    // Value already exists, won't insert
    map.entry("apple").or_insert(10);
    println!("apple (unchanged): {:?}", map.get(&"apple"));
    
    // or_insert_with - lazy insertion
    map.entry("banana").or_insert_with(|| {
        println!("Computing value for banana...");
        42
    });
    println!("banana: {:?}", map.get(&"banana"));
    
    // and_modify - modify existing value
    map.entry("apple").and_modify(|v| *v *= 2);
    println!("apple (doubled): {:?}", map.get(&"apple"));
    
    // Complex entry operations
    map.entry("cherry")
        .or_insert(0)
        .and_modify(|v| *v += 1);
    
    println!("cherry: {:?}", map.get(&"cherry"));
}

Atomic Operations

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // add - increment existing value
    map.insert("counter", 10);
    let prev = map.add("counter", 5);
    println!("Previous value: {:?}", prev);
    println!("New value: {:?}", map.get(&"counter"));
    
    // add with missing key
    let prev = map.add("new_key", 100);
    println!("Previous for new key: {:?}", prev);
    println!("New key value: {:?}", map.get(&"new_key"));
    
    // compute_if_absent
    let value = map.compute_if_absent("absent_key", |_| 42);
    println!("Computed: {}", value);
    
    // compute_if_present
    map.compute_if_present("counter", |_, v| Some(v + 100));
    println!("Counter updated: {:?}", map.get(&"counter"));
}

Iteration

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, &str> = DashMap::new();
    
    for i in 0..5 {
        map.insert(i, &format!("value-{}", i));
    }
    
    // Immutable iteration
    println!("Iterating:");
    for entry in map.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
    
    // Mutable iteration
    println!("\nModifying values:");
    for mut entry in map.iter_mut() {
        *entry.value_mut() = &format!("modified-{}", entry.key());
    }
    
    // Check modified values
    for entry in map.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
}

Iteration with Keys or Values Only

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // Iterate over keys
    println!("Keys:");
    for key in map.iter().map(|e| e.key().clone()) {
        println!("  {}", key);
    }
    
    // Iterate over values
    println!("\nValues:");
    for value in map.iter().map(|e| *e.value()) {
        println!("  {}", value);
    }
}

Removing Entries

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = 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 condition matches
    map.remove_if("c", |_k, v| *v > 5);
    println!("After remove_if (c not removed): {:?}", map.get(&"c"));
    
    map.remove_if("c", |_k, v| *v == 3);
    println!("After remove_if (c removed): {:?}", map.get(&"c"));
    
    // Remove entry
    let entry = map.entry("a");
    entry.remove();
    println!("After entry.remove: {:?}", map.get(&"a"));
}

Real-World: Thread-Safe Counter

use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
 
fn main() {
    let counters = Arc::new(DashMap::new());
    
    let handles: Vec<_> = (0..100)
        .map(|i| {
            let counters = Arc::clone(&counters);
            thread::spawn(move || {
                // Each thread increments multiple counters
                for j in 0..10 {
                    let key = format!("counter-{}", j % 5);
                    counters.entry(key).and_modify(|v| *v += 1).or_insert(1);
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    // Print final counts
    println!("Final counts:");
    for entry in counters.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
    
    // Verify total
    let total: i32 = counters.iter().map(|e| *e.value()).sum();
    println!("Total: {}", total);
}

Real-World: Concurrent Cache

use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
 
struct CacheEntry {
    value: String,
    created_at: Instant,
    ttl: Duration,
}
 
impl CacheEntry {
    fn new(value: String, ttl: Duration) -> Self {
        Self {
            value,
            created_at: Instant::now(),
            ttl,
        }
    }
    
    fn is_valid(&self) -> bool {
        Instant::now().duration_since(self.created_at) < self.ttl
    }
}
 
struct Cache {
    data: DashMap<String, CacheEntry>,
    default_ttl: Duration,
}
 
impl Cache {
    fn new(default_ttl: Duration) -> Self {
        Self {
            data: DashMap::new(),
            default_ttl,
        }
    }
    
    fn get(&self, key: &str) -> Option<String> {
        if let Some(entry) = self.data.get(key) {
            if entry.value().is_valid() {
                return Some(entry.value().value.clone());
            }
        }
        None
    }
    
    fn set(&self, key: String, value: String) {
        self.set_with_ttl(key, value, self.default_ttl);
    }
    
    fn set_with_ttl(&self, key: String, value: String, ttl: Duration) {
        self.data.insert(key, CacheEntry::new(value, ttl));
    }
    
    fn invalidate(&self, key: &str) {
        self.data.remove(key);
    }
    
    fn cleanup(&self) {
        self.data.retain(|_, entry| entry.is_valid());
    }
}
 
fn expensive_computation(key: &str) -> String {
    thread::sleep(Duration::from_millis(100));
    format!("result-for-{}", key)
}
 
fn main() {
    let cache = Arc::new(Cache::new(Duration::from_secs(60)));
    
    let handles: Vec<_> = (0..5)
        .map(|i| {
            let cache = Arc::clone(&cache);
            thread::spawn(move || {
                let key = format!("key-{}", i % 3);
                
                // Try cache first
                if let Some(value) = cache.get(&key) {
                    println!("Thread {} got cached: {}", i, value);
                    return;
                }
                
                // Compute and cache
                let value = expensive_computation(&key);
                cache.set(key.clone(), value.clone());
                println!("Thread {} computed: {}", i, value);
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Real-World: Request Rate Tracker

use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
 
struct RateTracker {
    requests: DashMap<String, Vec<Instant>>,
    max_requests: usize,
    window: Duration,
}
 
impl RateTracker {
    fn new(max_requests: usize, window: Duration) -> Self {
        Self {
            requests: DashMap::new(),
            max_requests,
            window,
        }
    }
    
    fn is_allowed(&self, key: &str) -> bool {
        let now = Instant::now();
        let cutoff = now - self.window;
        
        // Get or create entry
        let mut entry = self.requests.entry(key.to_string()).or_insert_with(Vec::new);
        
        // Remove old requests
        entry.retain(|t| *t > cutoff);
        
        // Check if under limit
        if entry.len() < self.max_requests {
            entry.push(now);
            true
        } else {
            false
        }
    }
    
    fn remaining(&self, key: &str) -> usize {
        let now = Instant::now();
        let cutoff = now - self.window;
        
        if let Some(mut entry) = self.requests.get_mut(key) {
            entry.retain(|t| *t > cutoff);
            self.max_requests.saturating_sub(entry.len())
        } else {
            self.max_requests
        }
    }
}
 
fn main() {
    let tracker = Arc::new(RateTracker::new(5, Duration::from_secs(60)));
    
    let handles: Vec<_> = (0..10)
        .map(|i| {
            let tracker = Arc::clone(&tracker);
            thread::spawn(move || {
                let user = "user_123";
                if tracker.is_allowed(user) {
                    println!("Request {} allowed (remaining: {})", i, tracker.remaining(user));
                } else {
                    println!("Request {} rate limited!", i);
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Real-World: Pub/Sub System

use dashmap::DashMap;
use std::sync::Arc;
use std::collections::HashSet;
 
struct PubSub {
    // Map from topic to set of subscribers
    subscriptions: DashMap<String, HashSet<String>>,
    // Map from subscriber to their messages
    messages: DashMap<String, Vec<String>>,
}
 
impl PubSub {
    fn new() -> Self {
        Self {
            subscriptions: DashMap::new(),
            messages: DashMap::new(),
        }
    }
    
    fn subscribe(&self, topic: &str, subscriber: &str) {
        self.subscriptions
            .entry(topic.to_string())
            .or_insert_with(HashSet::new)
            .insert(subscriber.to_string());
        
        println!("{} subscribed to {}", subscriber, topic);
    }
    
    fn unsubscribe(&self, topic: &str, subscriber: &str) {
        if let Some(mut subs) = self.subscriptions.get_mut(topic) {
            subs.remove(subscriber);
        }
    }
    
    fn publish(&self, topic: &str, message: &str) {
        if let Some(subs) = self.subscriptions.get(topic) {
            for subscriber in subs.iter() {
                self.messages
                    .entry(subscriber.clone())
                    .or_insert_with(Vec::new)
                    .push(format!("[{}] {}", topic, message));
            }
        }
    }
    
    fn get_messages(&self, subscriber: &str) -> Vec<String> {
        if let Some(mut msgs) = self.messages.get_mut(subscriber) {
            std::mem::take(&mut *msgs)
        } else {
            Vec::new()
        }
    }
}
 
fn main() {
    let pubsub = Arc::new(PubSub::new());
    
    // Subscribe
    pubsub.subscribe("news", "alice");
    pubsub.subscribe("news", "bob");
    pubsub.subscribe("sports", "bob");
    
    // Publish
    pubsub.publish("news", "Breaking: Rust 2.0 released!");
    pubsub.publish("sports", "Team wins championship!");
    
    // Get messages
    println!("\nAlice's messages: {:?}", pubsub.get_messages("alice"));
    println!("Bob's messages: {:?}", pubsub.get_messages("bob"));
}

Retain and Filter

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, i32> = DashMap::new();
    
    for i in 0..20 {
        map.insert(i, i * i);
    }
    
    println!("Before retain: {}", map.len());
    
    // Retain only entries where key is even and value > 100
    map.retain(|k, v| k % 2 == 0 && *v > 100);
    
    println!("After retain: {}", map.len());
    for entry in map.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
}

Shards Information

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, String> = DashMap::with_shard_amount(16);
    
    println!("Number of shards: {}", map.shards().len());
    
    // Check shard capacity
    for (i, shard) in map.shards().iter().enumerate() {
        // Each shard is a RwLock<HashMap>
        println!("Shard {} capacity: {}", i, shard.read().len());
    }
    
    // Insert some data
    for i in 0..100 {
        map.insert(i, format!("value-{}", i));
    }
    
    // Check distribution
    println!("\nAfter inserting 100 items:");
    for (i, shard) in map.shards().iter().enumerate() {
        println!("Shard {} entries: {}", i, shard.read().len());
    }
}

Compare and Swap Pattern

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    map.insert("counter", 0);
    
    // Simulated compare-and-swap
    fn cas(map: &DashMap<&str, i32>, key: &str, expected: i32, new: i32) -> bool {
        if let Some(mut value) = map.get_mut(key) {
            if *value == expected {
                *value = new;
                return true;
            }
        }
        false
    }
    
    // Try to update from 0 to 10
    println!("CAS 0->10: {}", cas(&map, "counter", 0, 10));
    println!("Counter: {:?}", map.get("counter"));
    
    // Try wrong expected value
    println!("CAS 0->20: {}", cas(&map, "counter", 0, 20));
    println!("Counter: {:?}", map.get("counter"));
    
    // Correct expected value
    println!("CAS 10->20: {}", cas(&map, "counter", 10, 20));
    println!("Counter: {:?}", map.get("counter"));
}

Real-World: Session Store

use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
 
#[derive(Clone)]
struct Session {
    user_id: String,
    created_at: Instant,
    expires_at: Instant,
    data: std::collections::HashMap<String, String>,
}
 
impl Session {
    fn new(user_id: String, ttl: Duration) -> Self {
        let now = Instant::now();
        Self {
            user_id,
            created_at: now,
            expires_at: now + ttl,
            data: std::collections::HashMap::new(),
        }
    }
    
    fn is_valid(&self) -> bool {
        Instant::now() < self.expires_at
    }
}
 
struct SessionStore {
    sessions: DashMap<String, Session>,
    default_ttl: Duration,
}
 
impl SessionStore {
    fn new(default_ttl: Duration) -> Self {
        Self {
            sessions: DashMap::new(),
            default_ttl,
        }
    }
    
    fn create_session(&self, user_id: String) -> String {
        let session_id = format!("session-{}-{}", user_id, rand_id());
        let session = Session::new(user_id, self.default_ttl);
        self.sessions.insert(session_id.clone(), session);
        session_id
    }
    
    fn get_session(&self, session_id: &str) -> Option<Session> {
        self.sessions.get(session_id).and_then(|s| {
            if s.value().is_valid() {
                Some(s.value().clone())
            } else {
                None
            }
        })
    }
    
    fn set_data(&self, session_id: &str, key: String, value: String) -> bool {
        if let Some(mut session) = self.sessions.get_mut(session_id) {
            session.data.insert(key, value);
            true
        } else {
            false
        }
    }
    
    fn get_data(&self, session_id: &str, key: &str) -> Option<String> {
        self.sessions.get(session_id).and_then(|s| {
            s.value().data.get(key).cloned()
        })
    }
    
    fn destroy_session(&self, session_id: &str) {
        self.sessions.remove(session_id);
    }
    
    fn cleanup_expired(&self) {
        self.sessions.retain(|_, session| session.is_valid());
    }
}
 
fn rand_id() -> u64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos() as u64
}
 
fn main() {
    let store = SessionStore::new(Duration::from_secs(3600));
    
    // Create session
    let session_id = store.create_session("user_123".to_string());
    println!("Created session: {}", session_id);
    
    // Set session data
    store.set_data(&session_id, "theme".to_string(), "dark".to_string());
    store.set_data(&session_id, "lang".to_string(), "en".to_string());
    
    // Get session data
    if let Some(session) = store.get_session(&session_id) {
        println!("User: {}", session.user_id);
        println!("Theme: {:?}", store.get_data(&session_id, "theme"));
        println!("Lang: {:?}", store.get_data(&session_id, "lang"));
    }
    
    // Destroy session
    store.destroy_session(&session_id);
    println!("Session destroyed");
}

Stats and Introspection

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // Length
    println!("Length: {}", map.len());
    
    // Is empty
    println!("Is empty: {}", map.is_empty());
    
    // Capacity
    println!("Capacity: {}", map.capacity());
    
    // Contains key
    println!("Contains 'a': {}", map.contains_key(&"a"));
    println!("Contains 'x': {}", map.contains_key(&"x"));
    
    // Verify all entries
    let mut count = 0;
    for _ in map.iter() {
        count += 1;
    }
    println!("Verified {} entries via iteration", count);
}

Clone and Extend

use dashmap::DashMap;
 
fn main() {
    let map1: DashMap<i32, &str> = DashMap::new();
    map1.insert(1, "one");
    map1.insert(2, "two");
    
    // Clone
    let map2 = map1.clone();
    println!("Cloned map:");
    for entry in map2.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
    
    // Extend
    let more = vec![(3, "three"), (4, "four")];
    map1.extend(more);
    
    println!("\nExtended map:");
    for entry in map1.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
}

Real-World: Metrics Collector

use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
 
#[derive(Clone)]
struct Metric {
    name: String,
    value: f64,
    count: u64,
}
 
impl Metric {
    fn new(name: String) -> Self {
        Self { name, value: 0.0, count: 0 }
    }
    
    fn record(&mut self, value: f64) {
        // Running average
        self.count += 1;
        self.value += (value - self.value) / self.count as f64;
    }
}
 
struct MetricsCollector {
    metrics: DashMap<String, Metric>,
}
 
impl MetricsCollector {
    fn new() -> Self {
        Self { metrics: DashMap::new() }
    }
    
    fn record(&self, name: &str, value: f64) {
        self.metrics
            .entry(name.to_string())
            .and_modify(|m| m.record(value))
            .or_insert_with(|| {
                let mut m = Metric::new(name.to_string());
                m.record(value);
                m
            });
    }
    
    fn get(&self, name: &str) -> Option<Metric> {
        self.metrics.get(name).map(|m| m.value().clone())
    }
    
    fn all_metrics(&self) -> Vec<Metric> {
        self.metrics.iter().map(|e| e.value().clone()).collect()
    }
    
    fn summary(&self) {
        println!("Metrics Summary:");
        for entry in self.metrics.iter() {
            let m = entry.value();
            println!("  {}: avg={:.2} (n={})", m.name, m.value, m.count);
        }
    }
}
 
fn main() {
    let collector = Arc::new(MetricsCollector::new());
    
    let handles: Vec<_> = (0..10)
        .map(|i| {
            let collector = Arc::clone(&collector);
            thread::spawn(move || {
                for j in 0..100 {
                    collector.record("response_time", (i * 10 + j) as f64);
                    collector.record("request_size", 100.0 + i as f64);
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    collector.summary();
}

Summary

  • DashMap::new() creates a concurrent HashMap
  • map.insert(key, value) inserts a key-value pair
  • map.get(&key) returns a reference guard with key() and value() methods
  • map.get_mut(&key) returns a mutable reference guard
  • map.entry(key) provides the entry API for conditional operations
  • map.iter() and map.iter_mut() for iteration
  • map.retain(predicate) filters entries in place
  • map.remove(&key) removes an entry
  • Sharding enables high concurrent throughput
  • Use Arc<DashMap> for sharing across threads
  • Perfect for: concurrent caches, counters, session stores, metrics
  • Thread-safe without explicit locking in most cases