How do I use concurrent maps with DashMap in Rust?

Walkthrough

The dashmap crate provides a blazing-fast concurrent hash map for Rust. Unlike standard HashMap wrapped in a Mutex or RwLock, DashMap uses lock sharding to allow multiple threads to access different parts of the map simultaneously with minimal contention. Each shard has its own lock, so operations on different keys typically don't block each other. This makes DashMap ideal for high-concurrency scenarios like web servers, caches, connection pools, and any situation where multiple threads need to read and write to a shared map frequently.

Key concepts:

  1. Lock sharding — multiple locks for different map regions, reducing contention
  2. Interior mutability — no need for Mutex<Map> wrapper
  3. Zero-cost lookups — references returned directly without copying
  4. Thread-safe by design — Send + Sync out of the box
  5. Rich API — entry API, iteration, atomic operations

Code Example

# Cargo.toml
[dependencies]
dashmap = "5"
use dashmap::DashMap;
 
fn main() {
    let map = DashMap::new();
    map.insert("key", "value");
    
    if let Some(value) = map.get("key") {
        println!("Found: {}", value);
    }
}

Creating a DashMap

use dashmap::DashMap;
use std::collections::hash_map::RandomState;
 
fn main() {
    // Create with default settings
    let map: DashMap<i32, String> = DashMap::new();
    println!("Empty map, len: {}", map.len());
    
    // Create with capacity
    let map: DashMap<i32, i32> = DashMap::with_capacity(100);
    println!("With capacity 100, len: {}", map.len());
    
    // Create with number of shards (must be power of 2)
    // More shards = less contention but more memory overhead
    let map: DashMap<i32, i32> = DashMap::with_shards(16);
    println!("With 16 shards");
    
    // Create with capacity and shards
    let map: DashMap<i32, i32> = DashMap::with_capacity_and_shards(1000, 32);
    
    // With custom hasher
    let map: DashMap<i32, i32, RandomState> = DashMap::with_hasher(RandomState::new());
}

Basic Operations

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // Insert values
    map.insert("one", 1);
    map.insert("two", 2);
    map.insert("three", 3);
    
    println!("Length: {}", map.len());
    
    // Get value (returns reference wrapper)
    if let Some(value) = map.get("one") {
        println!("Got 'one': {}", *value);
    }
    
    // Check if key exists
    println!("Contains 'two': {}", map.contains_key("two"));
    
    // Remove entry
    let removed = map.remove("two");
    println!("Removed: {:?}", removed);
    
    // Get and remove atomically
    let entry = map.entry("three");
    println!("Entry for 'three': {:?}", entry.key());
    
    // Clear all
    map.clear();
    println!("After clear, len: {}", map.len());
}

Working with References

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, String> = DashMap::new();
    map.insert(1, "hello".to_string());
    map.insert(2, "world".to_string());
    
    // get() returns Option<Ref<K, V>> - a guard that holds the lock
    if let Some(ref_value) = map.get(&1) {
        // ref_value is like a smart pointer
        println!("Key: {}, Value: {}", ref_value.key(), ref_value.value());
        
        // Dereference to access the value
        let value: &String = &*ref_value;
        println!("Deref'd value: {}", value);
    }
    // Lock released when ref_value goes out of scope
    
    // get_mut() returns Option<RefMut<K, V>> for mutation
    if let Some(mut ref_mut) = map.get_mut(&2) {
        ref_mut.value_mut().push_str("!");
        println!("Modified: {}", ref_mut.value());
    }
}

The Entry API

use dashmap::DashMap;
use dashmap::mapref::one::Ref;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // entry() returns an Entry enum similar to HashMap
    use dashmap::Entry::*;
    
    match map.entry("count") {
        Occupied(entry) => {
            println!("Already exists: {}", entry.get());
        }
        Vacant(entry) => {
            entry.insert(1);
            println!("Inserted new value");
        }
    }
    
    // or_insert() - insert if vacant
    map.entry("counter").or_insert(0);
    println!("Counter: {:?}", map.get("counter"));
    
    // or_insert_with() - lazy initialization
    map.entry("lazy").or_insert_with(|| {
        println!("Computing expensive value...");
        42
    });
    
    // and_modify() - modify if occupied
    map.entry("counter").and_modify(|v| *v += 1);
    println!("Counter after modify: {:?}", map.get("counter"));
    
    // or_default() - insert default if vacant
    map.entry("default").or_default();
    println!("Default: {:?}", map.get("default"));
}

Iteration

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, &str> = DashMap::new();
    map.insert(1, "one");
    map.insert(2, "two");
    map.insert(3, "three");
    
    // Iterate (locks individual entries as visited)
    println!("Iterating:");
    for entry in map.iter() {
        println!("  {} -> {}", entry.key(), entry.value());
    }
    
    // Mutable iteration
    println!("\nUppercase:");
    for mut entry in map.iter_mut() {
        entry.value_mut().make_ascii_uppercase();
        println!("  {} -> {}", entry.key(), entry.value());
    }
    
    // Keys iterator
    println!("\nKeys: {:?}", map.keys().collect::<Vec<_>>());
    
    // Values iterator
    println!("Values: {:?}", map.values().collect::<Vec<_>>());
}

Concurrent Access

use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
 
fn main() {
    let map = Arc::new(DashMap::new());
    
    let mut handles = vec![];
    
    // Spawn multiple writers
    for i in 0..4 {
        let map = Arc::clone(&map);
        let handle = thread::spawn(move || {
            for j in 0..1000 {
                let key = format!("thread_{}_key_{}", i, j);
                map.insert(key, i * 1000 + j);
            }
        });
        handles.push(handle);
    }
    
    // Spawn readers
    for _ in 0..2 {
        let map = Arc::clone(&map);
        let handle = thread::spawn(move || {
            for i in 0..500 {
                let key = format!("thread_0_key_{}", i);
                if let Some(value) = map.get(&key) {
                    // Do something with value
                    let _ = *value;
                }
            }
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Final map size: {}", map.len());
}

Atomic Updates

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    map.insert("counter", 0);
    
    // Update using entry API
    for _ in 0..10 {
        map.entry("counter").and_modify(|v| *v += 1).or_insert(1);
    }
    println!("Counter: {:?}", map.get("counter"));
    
    // Fetch and update atomically
    let old_value = map.alter("counter", |_, v| v * 2);
    println!("After alter: {:?}", map.get("counter"));
    
    // Compare and swap pattern
    loop {
        if let Some(mut entry) = map.get_mut("counter") {
            let current = *entry.value();
            if current == 20 {
                *entry.value_mut() = 100;
                break;
            }
        }
    }
    println!("After CAS: {:?}", map.get("counter"));
}

Bulk Operations

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, String> = DashMap::new();
    
    // Extend with iterator
    map.extend([
        (1, "one".to_string()),
        (2, "two".to_string()),
        (3, "three".to_string()),
    ]);
    
    println!("After extend: {} entries", map.len());
    
    // Retain only matching entries
    map.retain(|k, _| *k < 3);
    println!("After retain: {} entries", map.len());
    
    // Shrink to fit (may reduce memory)
    map.shrink_to_fit();
    
    // Remove entries matching predicate
    map.retain(|_, _| true); // Keep all
}

Computing Missing Values

use dashmap::DashMap;
 
fn main() {
    let cache: DashMap<String, u64> = DashMap::new();
    
    fn expensive_computation(key: &str) -> u64 {
        println!("Computing for '{}'...", key);
        key.len() as u64 * 100
    }
    
    // Get or compute
    fn get_or_compute(cache: &DashMap<String, u64>, key: &str) -> u64 {
        if let Some(value) = cache.get(key) {
            return *value;
        }
        
        let computed = expensive_computation(key);
        cache.insert(key.to_string(), computed);
        computed
    }
    
    // First call - computes
    let val1 = get_or_compute(&cache, "hello");
    println!("Result: {}", val1);
    
    // Second call - cached
    let val2 = get_or_compute(&cache, "hello");
    println!("Result: {}", val2);
    
    // Using entry API for get-or-insert
    let val = cache.entry("world".to_string()).or_insert_with(|| {
        expensive_computation("world")
    });
    println!("World: {}", val);
}

Web Server Counter Example

use dashmap::DashMap;
use std::sync::Arc;
 
struct AppState {
    counters: DashMap<String, u64>,
}
 
impl AppState {
    fn new() -> Self {
        Self {
            counters: DashMap::new(),
        }
    }
    
    fn increment(&self, key: &str) -> u64 {
        self.counters
            .entry(key.to_string())
            .and_modify(|v| *v += 1)
            .or_insert(1)
    }
    
    fn get(&self, key: &str) -> u64 {
        self.counters.get(key).map(|v| *v).unwrap_or(0)
    }
    
    fn get_all(&self) -> Vec<(String, u64)> {
        self.counters
            .iter()
            .map(|entry| (entry.key().clone(), *entry.value()))
            .collect()
    }
}
 
fn main() {
    let state = Arc::new(AppState::new());
    
    // Simulate concurrent requests
    let handles: Vec<_> = (0..10)
        .map(|i| {
            let state = Arc::clone(&state);
            std::thread::spawn(move || {
                for _ in 0..100 {
                    state.increment(&format!("user_{}", i % 3));
                }
            })
        })
        .collect();
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Final counters:");
    for (key, count) in state.get_all() {
        println!("  {}: {}", key, count);
    }
}

Connection Pool Example

use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
 
#[derive(Debug)]
struct Connection {
    id: u64,
    created: Instant,
}
 
impl Connection {
    fn new(id: u64) -> Self {
        Self {
            id,
            created: Instant::now(),
        }
    }
    
    fn is_stale(&self, max_age: Duration) -> bool {
        self.created.elapsed() > max_age
    }
}
 
struct ConnectionPool {
    connections: DashMap<String, Connection>,
    next_id: std::sync::atomic::AtomicU64,
}
 
impl ConnectionPool {
    fn new() -> Self {
        Self {
            connections: DashMap::new(),
            next_id: std::sync::atomic::AtomicU64::new(1),
        }
    }
    
    fn get(&self, name: &str) -> Option<Connection> {
        self.connections.get(name).map(|c| Connection {
            id: c.id,
            created: c.created,
        })
    }
    
    fn get_or_create(&self, name: &str) -> Connection {
        let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
        self.connections
            .entry(name.to_string())
            .or_insert_with(|| Connection::new(id))
            .clone()
    }
    
    fn remove_stale(&self, max_age: Duration) {
        self.connections.retain(|_, conn| !conn.is_stale(max_age));
    }
    
    fn list(&self) -> Vec<(String, u64)> {
        self.connections
            .iter()
            .map(|e| (e.key().clone(), e.id))
            .collect()
    }
}
 
fn main() {
    let pool = Arc::new(ConnectionPool::new());
    
    // Create connections
    pool.get_or_create("db1");
    pool.get_or_create("db2");
    
    println!("Connections: {:?}", pool.list());
    
    // Simulate some time passing (connections would be stale)
    // In real code, use Instant::now() checks
}

Rate Limiter with DashMap

use dashmap::DashMap;
use std::time::{Duration, Instant};
 
#[derive(Clone)]
struct RateLimitEntry {
    count: u32,
    window_start: Instant,
}
 
pub struct RateLimiter {
    entries: DashMap<String, RateLimitEntry>,
    max_requests: u32,
    window: Duration,
}
 
impl RateLimiter {
    pub fn new(max_requests: u32, window: Duration) -> Self {
        Self {
            entries: DashMap::new(),
            max_requests,
            window,
        }
    }
    
    pub fn check(&self, client_id: &str) -> bool {
        let now = Instant::now();
        
        self.entries
            .entry(client_id.to_string())
            .and_modify(|entry| {
                // Reset if window expired
                if now.duration_since(entry.window_start) > self.window {
                    entry.count = 0;
                    entry.window_start = now;
                }
                entry.count += 1;
            })
            .or_insert_with(|| RateLimitEntry {
                count: 1,
                window_start: now,
            });
        
        // Check if under limit
        self.entries
            .get(client_id)
            .map(|e| e.count <= self.max_requests)
            .unwrap_or(true)
    }
    
    pub fn remaining(&self, client_id: &str) -> u32 {
        let now = Instant::now();
        
        self.entries
            .get(client_id)
            .map(|entry| {
                if now.duration_since(entry.window_start) > self.window {
                    self.max_requests
                } else {
                    self.max_requests.saturating_sub(entry.count)
                }
            })
            .unwrap_or(self.max_requests)
    }
    
    pub fn cleanup_expired(&self) {
        let now = Instant::now();
        self.entries.retain(|_, entry| {
            now.duration_since(entry.window_start) <= self.window
        });
    }
}
 
fn main() {
    let limiter = RateLimiter::new(5, Duration::from_secs(60));
    
    let client = "user_123";
    
    for i in 0..7 {
        let allowed = limiter.check(client);
        let remaining = limiter.remaining(client);
        println!("Request {}: allowed={}, remaining={}", i + 1, allowed, remaining);
    }
}

Session Store Example

use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::time::{Duration, Instant};
 
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Session {
    user_id: u64,
    username: String,
    created_at: Instant,
    last_activity: Instant,
    data: serde_json::Value,
}
 
pub struct SessionStore {
    sessions: DashMap<String, Session>,
    timeout: Duration,
}
 
impl SessionStore {
    pub fn new(timeout: Duration) -> Self {
        Self {
            sessions: DashMap::new(),
            timeout,
        }
    }
    
    pub fn create_session(&self, user_id: u64, username: String) -> String {
        let session_id = uuid::Uuid::new_v4().to_string();
        let now = Instant::now();
        
        let session = Session {
            user_id,
            username,
            created_at: now,
            last_activity: now,
            data: serde_json::json!({}),
        };
        
        self.sessions.insert(session_id.clone(), session);
        session_id
    }
    
    pub fn get_session(&self, session_id: &str) -> Option<Session> {
        self.sessions.get(session_id).map(|s| s.clone())
    }
    
    pub fn touch_session(&self, session_id: &str) -> bool {
        if let Some(mut session) = self.sessions.get_mut(session_id) {
            session.last_activity = Instant::now();
            true
        } else {
            false
        }
    }
    
    pub fn set_session_data(&self, session_id: &str, key: &str, value: serde_json::Value) -> bool {
        if let Some(mut session) = self.sessions.get_mut(session_id) {
            if let Some(obj) = session.data.as_object_mut() {
                obj.insert(key.to_string(), value);
                return true;
            }
        }
        false
    }
    
    pub fn destroy_session(&self, session_id: &str) {
        self.sessions.remove(session_id);
    }
    
    pub fn cleanup_expired(&self) {
        let now = Instant::now();
        self.sessions.retain(|_, session| {
            now.duration_since(session.last_activity) < self.timeout
        });
    }
    
    pub fn session_count(&self) -> usize {
        self.sessions.len()
    }
}
 
// Note: This example requires uuid and serde_json dependencies
fn main() {
    let store = SessionStore::new(Duration::from_secs(3600));
    
    // Create a session
    let session_id = format!("test-session-123");
    let now = Instant::now();
    
    store.sessions.insert(session_id.clone(), Session {
        user_id: 42,
        username: "alice".to_string(),
        created_at: now,
        last_activity: now,
        data: serde_json::json!({"theme": "dark"}),
    });
    
    if let Some(session) = store.get_session(&session_id) {
        println!("User: {}", session.username);
    }
    
    println!("Active sessions: {}", store.session_count());
}

Configuration Cache Example

use dashmap::DashMap;
use std::path::PathBuf;
 
#[derive(Debug, Clone)]
struct Config {
    values: DashMap<String, String>,
}
 
impl Config {
    fn new() -> Self {
        Self {
            values: DashMap::new(),
        }
    }
    
    fn load_from_file(&self, path: &PathBuf) -> std::io::Result<()> {
        let contents = std::fs::read_to_string(path)?;
        
        for line in contents.lines() {
            let line = line.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }
            
            if let Some((key, value)) = line.split_once('=') {
                self.values.insert(key.trim().to_string(), value.trim().to_string());
            }
        }
        
        Ok(())
    }
    
    fn get(&self, key: &str) -> Option<String> {
        self.values.get(key).map(|v| v.clone())
    }
    
    fn set(&self, key: String, value: String) {
        self.values.insert(key, value);
    }
    
    fn remove(&self, key: &str) -> Option<String> {
        self.values.remove(key).map(|(_, v)| v)
    }
    
    fn keys(&self) -> Vec<String> {
        self.values.keys().map(|k| k.clone()).collect()
    }
}
 
fn main() {
    let config = Config::new();
    
    // Set some values
    config.set("database.host".to_string(), "localhost".to_string());
    config.set("database.port".to_string(), "5432".to_string());
    config.set("cache.ttl".to_string(), "3600".to_string());
    
    // Get values
    if let Some(host) = config.get("database.host") {
        println!("Database host: {}", host);
    }
    
    println!("All keys: {:?}", config.keys());
}

Metrics Collection

use dashmap::DashMap;
use std::sync::atomic::{AtomicU64, Ordering};
 
#[derive(Debug)]
struct Metric {
    name: String,
    counter: AtomicU64,
    gauge: AtomicU64,
}
 
impl Metric {
    fn new(name: String) -> Self {
        Self {
            name,
            counter: AtomicU64::new(0),
            gauge: AtomicU64::new(0),
        }
    }
    
    fn increment(&self) {
        self.counter.fetch_add(1, Ordering::Relaxed);
    }
    
    fn set_gauge(&self, value: u64) {
        self.gauge.store(value, Ordering::Relaxed);
    }
    
    fn get_counter(&self) -> u64 {
        self.counter.load(Ordering::Relaxed)
    }
    
    fn get_gauge(&self) -> u64 {
        self.gauge.load(Ordering::Relaxed)
    }
}
 
pub struct Metrics {
    metrics: DashMap<String, Metric>,
}
 
impl Metrics {
    pub fn new() -> Self {
        Self {
            metrics: DashMap::new(),
        }
    }
    
    pub fn increment(&self, name: &str) {
        self.metrics
            .entry(name.to_string())
            .or_insert_with(|| Metric::new(name.to_string()))
            .increment();
    }
    
    pub fn set_gauge(&self, name: &str, value: u64) {
        self.metrics
            .entry(name.to_string())
            .or_insert_with(|| Metric::new(name.to_string()))
            .set_gauge(value);
    }
    
    pub fn snapshot(&self) -> Vec<(String, u64, u64)> {
        self.metrics
            .iter()
            .map(|entry| {
                (
                    entry.key().clone(),
                    entry.get_counter(),
                    entry.get_gauge(),
                )
            })
            .collect()
    }
}
 
fn main() {
    let metrics = Metrics::new();
    
    // Record some metrics
    for _ in 0..100 {
        metrics.increment("requests_total");
    }
    
    metrics.set_gauge("active_connections", 42);
    
    // Get snapshot
    println!("Metrics:");
    for (name, counter, gauge) in metrics.snapshot() {
        println!("  {}: counter={}, gauge={}", name, counter, gauge);
    }
}

Dedupe Channel Example

use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
 
pub struct DedupeChannel<T> {
    seen: DashMap<T, Instant>,
    ttl: Duration,
}
 
impl<T: std::hash::Hash + Eq + Clone> DedupeChannel<T> {
    pub fn new(ttl: Duration) -> Self {
        Self {
            seen: DashMap::new(),
            ttl,
        }
    }
    
    // Returns true if this is a new (not seen) item
    pub fn check(&self, item: &T) -> bool {
        let now = Instant::now();
        
        if let Some(seen_at) = self.seen.get(item) {
            if now.duration_since(*seen_at) < self.ttl {
                return false; // Already seen
            }
        }
        
        self.seen.insert(item.clone(), now);
        true
    }
    
    pub fn cleanup(&self) {
        let now = Instant::now();
        self.seen.retain(|_, seen_at| now.duration_since(*seen_at) < self.ttl);
    }
    
    pub fn size(&self) -> usize {
        self.seen.len()
    }
}
 
fn main() {
    let dedupe: Arc<DedupeChannel<String>> = Arc::new(DedupeChannel::new(Duration::from_secs(60)));
    
    // Simulate processing messages
    let messages = vec!["msg1", "msg2", "msg1", "msg3", "msg2", "msg4"];
    
    for msg in messages {
        if dedupe.check(&msg.to_string()) {
            println!("Processing: {}", msg);
        } else {
            println!("Skipping duplicate: {}", msg);
        }
    }
}

Shard Inspection

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, String> = DashMap::with_shards(4);
    
    // Add some entries
    for i in 0..100 {
        map.insert(i, format!("value_{}", i));
    }
    
    // Get number of shards
    println!("Number of shards: {}", map.shards().len());
    
    // Inspect each shard
    let mut shard_sizes = vec![];
    for shard in map.shards() {
        let size = shard.read().len();
        shard_sizes.push(size);
    }
    
    println!("Shard sizes: {:?}", shard_sizes);
    println!("Total entries: {}", shard_sizes.iter().sum::<usize>());
}

Comparing with RwLock

use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::Instant;
 
fn main() {
    let iterations = 100_000;
    
    // DashMap benchmark
    let dashmap = Arc::new(DashMap::new());
    let start = Instant::now();
    
    let handles: Vec<_> = (0..4)
        .map(|t| {
            let map = Arc::clone(&dashmap);
            std::thread::spawn(move || {
                for i in 0..iterations / 4 {
                    let key = t * (iterations / 4) + i;
                    map.insert(key, i);
                    let _ = map.get(&key);
                }
            })
        })
        .collect();
    
    for h in handles {
        h.join().unwrap();
    }
    
    let dashmap_time = start.elapsed();
    
    // RwLock<HashMap> benchmark
    let rwlock_map = Arc::new(RwLock::new(HashMap::new()));
    let start = Instant::now();
    
    let handles: Vec<_> = (0..4)
        .map(|t| {
            let map = Arc::clone(&rwlock_map);
            std::thread::spawn(move || {
                for i in 0..iterations / 4 {
                    let key = t * (iterations / 4) + i;
                    {
                        let mut map = map.write().unwrap();
                        map.insert(key, i);
                    }
                    {
                        let map = map.read().unwrap();
                        let _ = map.get(&key);
                    }
                }
            })
        })
        .collect();
    
    for h in handles {
        h.join().unwrap();
    }
    
    let rwlock_time = start.elapsed();
    
    println!("DashMap: {:?}", dashmap_time);
    println!("RwLock<HashMap>: {:?}", rwlock_time);
}

Summary

  • DashMap::new() creates a concurrent hash map with default sharding
  • DashMap::with_shards(n) creates with custom shard count (power of 2)
  • map.insert(k, v) and map.get(&k) for basic operations
  • map.get(&k) returns Option<Ref<K, V>> — a lock guard
  • map.get_mut(&k) returns Option<RefMut<K, V>> for mutation
  • map.entry(k) provides the familiar entry API
  • map.iter() and map.iter_mut() for iteration
  • map.retain(predicate) for filtering entries
  • Lock sharding enables high concurrency with minimal contention
  • Interior mutability — no Mutex<Map> wrapper needed
  • Thread-safe by design (Send + Sync)
  • Perfect for: caches, counters, session stores, rate limiters, metrics, connection pools