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

Walkthrough

The dashmap crate provides a blazing-fast concurrent hash map for Rust. Unlike std::collections::HashMap which requires external locking for concurrent access, DashMap handles synchronization internally using sharded locking—dividing the map into segments (shards), each with its own lock. This allows multiple threads to access different parts of the map simultaneously without contention. It's perfect for high-throughput concurrent applications like caches, registries, and shared state.

Key concepts:

  1. Sharded locking — internal segmentation for high concurrency
  2. DashMap — the main concurrent hash map type
  3. Atomic operations — insert, remove, get without explicit locking
  4. Fine-grained accessentry API and references for complex operations
  5. Iterators — thread-safe iteration over elements

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);
    }
}

Basic Operations

use dashmap::DashMap;
 
fn main() {
    // Create a new DashMap
    let map: DashMap<i32, &str> = DashMap::new();
    
    // Create with capacity hint
    let map_with_capacity: DashMap<i32, String> = DashMap::with_capacity(100);
    
    // Insert values
    map.insert(1, "one");
    map.insert(2, "two");
    map.insert(3, "three");
    
    // Get values (returns a reference wrapper)
    if let Some(value) = map.get(&1) {
        println!("Key 1: {}", *value);
    }
    
    // Check if key exists
    println!("Contains 2: {}", map.contains_key(&2));
    
    // Remove a value
    let removed = map.remove(&3);
    println!("Removed: {:?}", removed);
    
    // Length
    println!("Length: {}", map.len());
    
    // Clear all entries
    map.clear();
    println!("After clear: {}", map.len());
}

Thread-Safe Concurrent Access

use dashmap::DashMap;
use std::thread;
 
fn main() {
    let map = DashMap::new();
    
    // Spawn multiple writers
    let mut handles = vec![];
    
    for i in 0..10 {
        let map = map.clone(); // DashMap is cheaply cloneable (Arc internally)
        let handle = thread::spawn(move || {
            for j in 0..100 {
                map.insert(i * 100 + j, format!("value_{}_{}", i, j));
            }
        });
        handles.push(handle);
    }
    
    // Wait for all writers
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("Total entries: {}", map.len());
    
    // Spawn multiple readers
    let mut readers = vec![];
    
    for i in 0..5 {
        let map = map.clone();
        let handle = thread::spawn(move || {
            let start = i * 200;
            let end = start + 100;
            for key in start..end {
                if let Some(value) = map.get(&key) {
                    println!("Thread {} found key {}: {}", i, key, *value);
                }
            }
        });
        readers.push(handle);
    }
    
    for handle in readers {
        handle.join().unwrap();
    }
}

Entry API

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    // Get or insert default
    let entry = map.entry("counter").or_insert(0);
    *entry += 1;
    drop(entry); // Must drop the reference to release lock
    
    // Get or insert with function
    map.entry("items")
        .or_insert_with(|| vec!["initial".to_string()]);
    
    // Check and modify atomically
    map.entry("counter")
        .and_modify(|v| *v += 1)
        .or_insert(1);
    
    // Get current value
    if let Some(counter) = map.get("counter") {
        println!("Counter: {}", *counter);
    }
}

Updating Values

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<String, i32> = DashMap::new();
    
    map.insert("score".to_string(), 100);
    
    // Method 1: Get, modify, drop (not atomic!)
    if let Some(mut score) = map.get_mut("score") {
        *score += 10;
    }
    
    // Method 2: Using alter for atomic update
    map.alter("score", |_, v| v + 5);
    
    // Method 3: Using entry API
    map.entry("score".to_string())
        .and_modify(|v| *v += 5)
        .or_insert(0);
    
    println!("Final score: {:?}", map.get("score"));
}

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 a reference guard (like RwLock's RwLockReadGuard)
    if let Some(reference) = map.get(&1) {
        // reference is a Ref<'_, K, V>
        let key = reference.key();
        let value = reference.value();
        println!("Key: {}, Value: {}", key, value);
    }
    
    // get_mut() returns a mutable reference
    if let Some(mut reference) = map.get_mut(&2) {
        reference.push('!');
    }
    
    println!("Updated: {:?}", map.get(&2));
    
    // Reference is automatically released when dropped
}

Iteration

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<&str, i32> = DashMap::new();
    
    map.insert("a", 1);
    map.insert("b", 2);
    map.insert("c", 3);
    
    // Immutable iteration
    println!("Iterating:");
    for entry in map.iter() {
        println!("  {} = {}", entry.key(), entry.value());
    }
    
    // Mutable iteration
    println!("\nMutating values:");
    for mut entry in map.iter_mut() {
        *entry.value_mut() *= 10;
        println!("  {} = {}", entry.key(), entry.value());
    }
    
    // Converting to iterator (locks entire map briefly)
    let entries: Vec<_> = map.into_iter().collect();
    println!("\nCollected: {:?}", entries);
}

Shard Management

use dashmap::DashMap;
 
fn main() {
    // Default is number of CPU cores * 4 shards
    let map: DashMap<i32, i32> = DashMap::new();
    println!("Default shards: {}", map.shards().len());
    
    // Create with specific number of shards
    let map: DashMap<i32, i32> = DashMap::with_shard_amount(32);
    println!("Custom shards: {}", map.shards().len());
    
    // Create with hasher
    use std::collections::hash_map::RandomState;
    let map: DashMap<i32, i32, RandomState> = 
        DashMap::with_hasher(RandomState::new());
    
    // Access individual shard (advanced use)
    // shards() returns a slice of the internal RwLocks
    println!("Shard count: {}", map.shards().len());
}

Real-World: Thread-Safe Cache

use dashmap::DashMap;
use std::time::{Duration, Instant};
use std::thread;
 
struct CacheEntry {
    value: String,
    expires_at: Instant,
}
 
struct Cache {
    data: DashMap<String, CacheEntry>,
    default_ttl: Duration,
}
 
impl Cache {
    fn new(default_ttl: Duration) -> Self {
        Cache {
            data: DashMap::new(),
            default_ttl,
        }
    }
    
    fn get(&self, key: &str) -> Option<String> {
        self.data.get(key).and_then(|entry| {
            if entry.value.expires_at > Instant::now() {
                Some(entry.value.value.clone())
            } else {
                None // Expired
            }
        })
    }
    
    fn set(&self, key: String, value: String) {
        let entry = CacheEntry {
            value,
            expires_at: Instant::now() + self.default_ttl,
        };
        self.data.insert(key, entry);
    }
    
    fn set_with_ttl(&self, key: String, value: String, ttl: Duration) {
        let entry = CacheEntry {
            value,
            expires_at: Instant::now() + ttl,
        };
        self.data.insert(key, entry);
    }
    
    fn delete(&self, key: &str) {
        self.data.remove(key);
    }
    
    fn cleanup_expired(&self) {
        let now = Instant::now();
        let expired_keys: Vec<_> = self.data
            .iter()
            .filter(|entry| entry.value().expires_at <= now)
            .map(|entry| entry.key().clone())
            .collect();
        
        for key in expired_keys {
            self.data.remove(&key);
        }
    }
}
 
fn main() {
    let cache = Cache::new(Duration::from_secs(60));
    
    // Concurrent writes
    let mut handles = vec![];
    
    for i in 0..5 {
        let cache = Cache { 
            data: cache.data.clone(),
            default_ttl: cache.default_ttl,
        };
        
        handles.push(thread::spawn(move || {
            cache.set(
                format!("key_{}", i),
                format!("value_{}", i)
            );
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    // Concurrent reads
    for i in 0..5 {
        if let Some(value) = cache.get(&format!("key_{}", i)) {
            println!("Found: {}", value);
        }
    }
}

Real-World: Rate Limiter

use dashmap::DashMap;
use std::time::{Duration, Instant};
 
struct RateLimiter {
    requests: DashMap<String, (u32, Instant)>,
    max_requests: u32,
    window: Duration,
}
 
impl RateLimiter {
    fn new(max_requests: u32, window: Duration) -> Self {
        RateLimiter {
            requests: DashMap::new(),
            max_requests,
            window,
        }
    }
    
    fn is_allowed(&self, client_id: &str) -> bool {
        let now = Instant::now();
        
        let mut entry = self.requests.entry(client_id.to_string());
        
        if let Some(existing) = entry.get_mut() {
            let (count, start_time) = existing.value_mut();
            
            if now.duration_since(*start_time) > self.window {
                // Reset window
                *count = 1;
                *start_time = now;
                true
            } else if *count < self.max_requests {
                *count += 1;
                true
            } else {
                false
            }
        } else {
            entry.insert((1, now));
            true
        }
    }
    
    fn remaining(&self, client_id: &str) -> Option<u32> {
        self.requests.get(client_id).map(|entry| {
            let (count, start_time) = entry.value();
            if Instant::now().duration_since(*start_time) > self.window {
                self.max_requests
            } else {
                self.max_requests.saturating_sub(*count)
            }
        })
    }
}
 
fn main() {
    let limiter = RateLimiter::new(3, Duration::from_secs(60));
    
    let client = "user_123";
    
    for i in 0..5 {
        if limiter.is_allowed(client) {
            let remaining = limiter.remaining(client).unwrap();
            println!("Request {} allowed ({} remaining)", i + 1, remaining);
        } else {
            println!("Request {} rate limited!", i + 1);
        }
    }
}

Real-World: Pub/Sub System

use dashmap::DashMap;
use std::sync::Arc;
use std::collections::VecDeque;
 
#[derive(Clone)]
struct Message {
    topic: String,
    payload: String,
}
 
class PubSub {
    subscribers: DashMap<String, VecDeque<Message>>,
}
 
impl PubSub {
    fn new() -> Self {
        PubSub {
            subscribers: DashMap::new(),
        }
    }
    
    fn subscribe(&self, topic: &str) {
        self.subscribers
            .entry(topic.to_string())
            .or_insert_with(VecDeque::new);
    }
    
    fn publish(&self, topic: &str, message: String) {
        if let Some(mut queue) = self.subscribers.get_mut(topic) {
            queue.push_back(Message {
                topic: topic.to_string(),
                payload: message,
            });
        }
    }
    
    fn receive(&self, topic: &str) -> Option<Message> {
        self.subscribers.get_mut(topic).and_then(|mut queue| {
            queue.pop_front()
        })
    }
}
 
fn main() {
    let pubsub = PubSub::new();
    
    pubsub.subscribe("news");
    pubsub.subscribe("alerts");
    
    pubsub.publish("news", "Breaking: Rust 2.0 announced!".to_string());
    pubsub.publish("alerts", "System maintenance at 3 AM".to_string());
    
    while let Some(msg) = pubsub.receive("news") {
        println!("News: {}", msg.payload);
    }
    
    while let Some(msg) = pubsub.receive("alerts") {
        println!("Alert: {}", msg.payload);
    }
}

Real-World: Connection Pool

use dashmap::DashMap;
use std::sync::atomic::{AtomicU64, Ordering};
 
struct Connection {
    id: u64,
    created_at: std::time::Instant,
}
 
impl Connection {
    fn new(id: u64) -> Self {
        Connection {
            id,
            created_at: std::time::Instant::now(),
        }
    }
}
 
class ConnectionPool {
    connections: DashMap<u64, Connection>,
    next_id: AtomicU64,
    max_connections: usize,
}
 
impl ConnectionPool {
    fn new(max_connections: usize) -> Self {
        ConnectionPool {
            connections: DashMap::new(),
            next_id: AtomicU64::new(1),
            max_connections,
        }
    }
    
    fn acquire(&self) -> Option<u64> {
        if self.connections.len() >= self.max_connections {
            return None;
        }
        
        let id = self.next_id.fetch_add(1, Ordering::SeqCst);
        self.connections.insert(id, Connection::new(id));
        Some(id)
    }
    
    fn release(&self, id: u64) {
        self.connections.remove(&id);
    }
    
    fn get(&self, id: u64) -> Option<Connection> {
        self.connections.get(&id).map(|c| Connection {
            id: c.id,
            created_at: c.created_at,
        })
    }
}
 
fn main() {
    let pool = ConnectionPool::new(3);
    
    let conn1 = pool.acquire().unwrap();
    let conn2 = pool.acquire().unwrap();
    let conn3 = pool.acquire().unwrap();
    
    println!("Active connections: {}", pool.connections.len());
    
    // This should fail - pool is full
    match pool.acquire() {
        Some(_) => println!("Unexpectedly got connection!"),
        None => println!("Pool full, cannot acquire more"),
    }
    
    pool.release(conn2);
    println!("After release: {}", pool.connections.len());
    
    let conn4 = pool.acquire().unwrap();
    println!("Acquired new connection: {}", conn4);
}

Comparing with std::collections::HashMap + Mutex

use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::Instant;
 
fn benchmark_dashmap(count: u32) -> u128 {
    let map = DashMap::new();
    let start = Instant::now();
    
    let mut handles = vec![];
    for i in 0..4 {
        let map = map.clone();
        handles.push(thread::spawn(move || {
            for j in 0..count {
                map.insert(i * count + j, format!("value_{}", j));
            }
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    start.elapsed().as_millis()
}
 
fn benchmark_mutex(count: u32) -> u128 {
    let map = Arc::new(Mutex::new(HashMap::new()));
    let start = Instant::now();
    
    let mut handles = vec![];
    for i in 0..4 {
        let map = map.clone();
        handles.push(thread::spawn(move || {
            for j in 0..count {
                let mut guard = map.lock().unwrap();
                guard.insert(i * count + j, format!("value_{}", j));
            }
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    start.elapsed().as_millis()
}
 
fn benchmark_rwlock(count: u32) -> u128 {
    let map = Arc::new(RwLock::new(HashMap::new()));
    let start = Instant::now();
    
    let mut handles = vec![];
    for i in 0..4 {
        let map = map.clone();
        handles.push(thread::spawn(move || {
            for j in 0..count {
                let mut guard = map.write().unwrap();
                guard.insert(i * count + j, format!("value_{}", j));
            }
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    start.elapsed().as_millis()
}
 
fn main() {
    let count = 100_000;
    
    println!("Benchmarking {} writes per thread (4 threads):", count);
    println!("  DashMap:   {}ms", benchmark_dashmap(count));
    println!("  Mutex:     {}ms", benchmark_mutex(count));
    println!("  RwLock:    {}ms", benchmark_rwlock(count));
}

Advanced: Custom Types as Keys

use dashmap::DashMap;
use std::hash::{Hash, Hasher};
 
#[derive(Debug, Clone, PartialEq, Eq)]
struct UserKey {
    tenant_id: u32,
    user_id: u32,
}
 
impl Hash for UserKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.tenant_id.hash(state);
        self.user_id.hash(state);
    }
}
 
#[derive(Debug, Clone)]
struct User {
    name: String,
    email: String,
}
 
fn main() {
    let users: DashMap<UserKey, User> = DashMap::new();
    
    users.insert(UserKey { tenant_id: 1, user_id: 100 }, User {
        name: "Alice".to_string(),
        email: "alice@example.com".to_string(),
    });
    
    users.insert(UserKey { tenant_id: 1, user_id: 101 }, User {
        name: "Bob".to_string(),
        email: "bob@example.com".to_string(),
    });
    
    let key = UserKey { tenant_id: 1, user_id: 100 };
    if let Some(user) = users.get(&key) {
        println!("User: {} ({})", user.name, user.email);
    }
}

Entry API Patterns

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<String, Vec<i32>> = DashMap::new();
    
    // Append to existing or create new
    map.entry("numbers".to_string())
        .or_insert_with(Vec::new)
        .push(1);
    
    map.entry("numbers".to_string())
        .and_modify(|v| v.push(2))
        .or_insert_with(|| vec![2]);
    
    // Another pattern using get_mut
    if let Some(mut entry) = map.get_mut("numbers") {
        entry.push(3);
    }
    
    println!("Numbers: {:?}", map.get("numbers"));
}

Set-like Behavior

use dashmap::DashSet;
 
fn main() {
    // DashSet is a DashMap with () values
    let set: DashSet<i32> = DashSet::new();
    
    set.insert(1);
    set.insert(2);
    set.insert(3);
    set.insert(1); // Duplicate, ignored
    
    println!("Contains 2: {}", set.contains(&2));
    
    for item in set.iter() {
        println!("Item: {}", *item);
    }
    
    set.remove(&2);
    println!("After removal, contains 2: {}", set.contains(&2));
}

Atomic Batch Operations

use dashmap::DashMap;
 
fn main() {
    let map: DashMap<i32, String> = DashMap::new();
    
    // Batch insert
    for i in 0..1000 {
        map.insert(i, format!("value_{}", i));
    }
    
    // Retain only even keys
    map.retain(|k, _| k % 2 == 0);
    
    println!("After retain: {} entries", map.len());
    
    // Shrink to fit
    map.shrink_to_fit();
    
    // Remove all matching a predicate
    map.remove_if(&50, |_, v| v.starts_with("value"));
}

Reading Configuration Concurrently

use dashmap::DashMap;
use std::thread;
 
struct Config {
    settings: DashMap<String, String>,
}
 
impl Config {
    fn new() -> Self {
        let settings = DashMap::new();
        settings.insert("host".to_string(), "localhost".to_string());
        settings.insert("port".to_string(), "8080".to_string());
        settings.insert("debug".to_string(), "true".to_string());
        
        Config { settings }
    }
    
    fn get(&self, key: &str) -> Option<String> {
        self.settings.get(key).map(|v| v.clone())
    }
    
    fn set(&self, key: String, value: String) {
        self.settings.insert(key, value);
    }
}
 
fn main() {
    let config = Config::new();
    
    // Multiple readers can access concurrently
    let mut handles = vec![];
    
    for i in 0..10 {
        let config_settings = config.settings.clone();
        handles.push(thread::spawn(move || {
            if let Some(host) = config_settings.get("host") {
                println!("Thread {}: host = {}", i, *host);
            }
            if let Some(port) = config_settings.get("port") {
                println!("Thread {}: port = {}", i, *port);
            }
        }));
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Summary

  • DashMap::new() creates a new concurrent hash map
  • insert(), get(), remove() for basic operations
  • get() and get_mut() return reference guards
  • entry() API for atomic check-and-modify operations
  • iter() and iter_mut() for thread-safe iteration
  • Internally sharded for high concurrency (shards = CPU cores × 4 by default)
  • Cheaply cloneable (Arc-wrapped internally)
  • Use DashSet for set-like behavior
  • retain() to filter elements atomically
  • Much faster than HashMap<Mutex> for concurrent workloads
  • Perfect for: caches, registries, rate limiters, connection pools, shared state