Loading page…
Rust walkthroughs
Loading page…
The dashmap crate provides a concurrent hash map implementation that allows lock-free reads and fine-grained locking for writes. Unlike wrapping a HashMap in a Mutex or RwLock, DashMap shards the map into multiple segments, each with its own lock. This allows concurrent operations on different keys to proceed in parallel, significantly improving performance in multi-threaded scenarios. It's ideal for caches, registries, and any shared mutable state accessed from multiple threads.
Key concepts:
# Cargo.toml
[dependencies]
dashmap = "5"use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let map = Arc::new(DashMap::new());
map.insert("key", 42);
if let Some(value) = map.get(&"key") {
println!("Value: {}", *value);
}
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// Insert entries
map.insert("apple", 1);
map.insert("banana", 2);
map.insert("cherry", 3);
// Get values
if let Some(value) = map.get(&"apple") {
println!("Apple: {}", *value);
}
// Check if key exists
println!("Contains 'banana': {}", map.contains_key(&"banana"));
// Remove entries
let removed = map.remove(&"banana");
println!("Removed: {:?}", removed);
// Length
println!("Length: {}", map.len());
// Check if empty
println!("Is empty: {}", map.is_empty());
// Clear all entries
map.clear();
println!("After clear: {}", map.len());
}use dashmap::DashMap;
fn main() {
// Default
let map: DashMap<i32, String> = DashMap::new();
// With capacity (pre-allocate buckets)
let map: DashMap<i32, String> = DashMap::with_capacity(1000);
// With shard amount (must be power of 2)
// More shards = more parallelism but higher memory overhead
let map: DashMap<i32, String> = DashMap::with_shard_amount(16);
// With capacity and shard amount
let map: DashMap<i32, String> = DashMap::with_capacity_and_shard_amount(1000, 32);
// Custom hasher
use std::collections::hash_map::RandomState;
let map: DashMap<i32, String, RandomState> =
DashMap::with_hasher(RandomState::new());
println!("Created maps with various configurations");
}use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let map = Arc::new(DashMap::new());
let mut handles = vec![];
// Spawn writers
for i in 0..10 {
let map = Arc::clone(&map);
let handle = thread::spawn(move || {
for j in 0..100 {
let key = format!("thread-{}-key-{}", i, j);
map.insert(key, i * 100 + j);
}
});
handles.push(handle);
}
// Spawn readers
for _ in 0..5 {
let map = Arc::clone(&map);
let handle = thread::spawn(move || {
for i in 0..10 {
for j in 0..100 {
let key = format!("thread-{}-key-{}", i, j);
if let Some(value) = map.get(&key) {
let _ = *value;
}
}
}
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Final map size: {}", map.len());
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// entry().or_insert() - insert if missing
let entry = map.entry("count".to_string()).or_insert(0);
println!("Count: {}", *entry);
// entry().or_insert_with() - lazy initialization
let entry = map.entry("lazy".to_string()).or_insert_with(|| {
println!("Computing value...");
42
});
println!("Lazy value: {}", *entry);
// and_modify() - modify if exists
map.entry("count".to_string())
.and_modify(|v| *v += 1)
.or_insert(1);
println!("Count after increment: {}", map.get(&"count".to_string()).unwrap());
// entry().or_default() - insert default if missing
let entry = map.entry("default".to_string()).or_default();
println!("Default: {}", *entry);
// key() and value() on entry
if let Some(entry) = map.get_key_value(&"count") {
println!("Key: {}, Value: {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("counter", 0);
// get_mut returns a reference that allows mutation
if let Some(mut value) = map.get_mut(&"counter") {
*value += 1;
}
println!("Counter: {}", map.get(&"counter").unwrap());
// Mutate with closure
map.alter(&"counter", |_, v| v * 2);
println!("After alter: {}", map.get(&"counter").unwrap());
// Atomic update
map.update(&"counter", |_, v| v + 10);
println!("After update: {}", map.get(&"counter").unwrap());
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);
// Iterate over entries
println!("Entries:");
for entry in map.iter() {
println!(" {} -> {}", entry.key(), entry.value());
}
// Iterate over keys
println!("\nKeys:");
for key in map.iter_keys() {
println!(" {}", key);
}
// Iterate over values
println!("\nValues:");
for value in map.iter_values() {
println!(" {}", value);
}
// Mutable iteration
for mut entry in map.iter_mut() {
*entry.value_mut() *= 2;
}
println!("\nAfter doubling:");
for entry in map.iter() {
println!(" {} -> {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
map.insert("key", "value");
// get() returns a reference guard
// The entry is read-locked while the guard exists
{
let _guard = map.get(&"key").unwrap();
println!("Holding read lock on 'key'");
// Other reads can proceed, writes will block
}
println!("Read lock released");
// get_mut() returns a write guard
{
let mut guard = map.get_mut(&"key").unwrap();
*guard = "modified";
println!("Holding write lock on 'key'");
}
println!("Write lock released");
// Check if entry is locked (for advanced use cases)
println!("Is 'key' locked: {}", map.is_locked());
}use dashmap::DashMap;
fn main() {
let map: DashMap<i32, String> = DashMap::with_shard_amount(16);
// Get the number of shards
println!("Shards: {}", map.shards().len());
// Determine which shard a key belongs to
let key = 42;
let shard_index = map.determine_map(&key).into_usize();
println!("Key {} is in shard {}", key, shard_index);
// Get a specific shard (advanced use)
// Note: This locks the shard
// Capacity per shard
println!("Capacity: {}", map.capacity());
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// insert() returns old value if present
let old = map.insert("key", 1);
println!("Old value: {:?}", old); // None
let old = map.insert("key", 2);
println!("Old value: {:?}", old); // Some(1)
// remove() returns the removed entry
let removed = map.remove(&"key");
println!("Removed: {:?}", removed);
// swap() - insert and return old value atomically
map.insert("a", 1);
let swapped = map.swap("a", 10);
println!("Swapped: {:?}", swapped);
println!("New value: {:?}", map.get(&"a"));
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// insert_if() - insert only if condition is met
map.insert("key", 1);
// Only insert if current value < 5
map.insert_if("key", 10, |_, current| *current < 5);
println!("After insert_if (< 5): {:?}", map.get(&"key"));
// Only insert if current value < 0 (false)
map.insert_if("key", 20, |_, current| *current < 0);
println!("After insert_if (< 0): {:?}", map.get(&"key"));
// remove_if() - remove only if condition is met
let removed = map.remove_if(&"key", |_, v| *v > 5);
println!("Removed (> 5): {:?}", removed);
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
// get_or_insert() - get or insert default
let value = map.get_or_insert("count", 0);
println!("Count: {}", *value);
// get_or_insert_with() - get or compute
let value = map.get_or_insert_with("computed", || {
println!("Computing...");
42
});
println!("Computed: {}", *value);
// Second call doesn't recompute
let value = map.get_or_insert_with("computed", || {
panic!("Should not be called");
});
println!("Cached: {}", *value);
}use dashmap::DashMap;
fn main() {
let map = DashMap::new();
for i in 0..10 {
map.insert(i, i * 10);
}
println!("Before retain: {} entries", map.len());
// Retain only even keys
map.retain(|key, value| {
println!("Checking {} -> {}", key, value);
key % 2 == 0
});
println!("\nAfter retain (even keys):");
for entry in map.iter() {
println!(" {} -> {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
use std::collections::HashMap;
fn main() {
let mut hash_map = HashMap::new();
hash_map.insert("a", 1);
hash_map.insert("b", 2);
hash_map.insert("c", 3);
// From HashMap to DashMap
let dash_map: DashMap<&str, i32> = DashMap::from_iter(hash_map.iter().map(|(k, v)| (*k, *v)));
println!("DashMap size: {}", dash_map.len());
// From DashMap to HashMap
let back_to_hash_map: HashMap<&str, i32> = dash_map.iter()
.map(|entry| (entry.key().clone(), *entry.value()))
.collect();
println!("HashMap size: {}", back_to_hash_map.len());
// Clone DashMap
let cloned = dash_map.clone();
println!("Cloned size: {}", cloned.len());
}use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Clone)]
struct CacheEntry<T> {
value: T,
expires_at: Instant,
}
pub struct Cache<K, V> {
map: DashMap<K, CacheEntry<V>>,
default_ttl: Duration,
}
impl<K: std::hash::Hash + Eq + Clone, V: Clone> Cache<K, V> {
pub fn new(default_ttl: Duration) -> Self {
Self {
map: DashMap::new(),
default_ttl,
}
}
pub fn get(&self, key: &K) -> Option<V> {
self.map.get(key).and_then(|entry| {
if entry.expires_at > Instant::now() {
Some(entry.value.clone())
} else {
None
}
})
}
pub fn set(&self, key: K, value: V) {
self.set_with_ttl(key, value, self.default_ttl);
}
pub fn set_with_ttl(&self, key: K, value: V, ttl: Duration) {
let entry = CacheEntry {
value,
expires_at: Instant::now() + ttl,
};
self.map.insert(key, entry);
}
pub fn delete(&self, key: &K) {
self.map.remove(key);
}
pub fn cleanup_expired(&self) {
let now = Instant::now();
self.map.retain(|_, entry| entry.expires_at > now);
}
}
fn main() {
let cache = Arc::new(Cache::new(Duration::from_secs(60)));
cache.set("user:1", "Alice".to_string());
cache.set("user:2", "Bob".to_string());
if let Some(name) = cache.get(&"user:1") {
println!("Found user: {}", name);
}
// Concurrent access
let cache_reader = Arc::clone(&cache);
let handle = std::thread::spawn(move || {
if let Some(name) = cache_reader.get(&"user:2") {
println!("Thread found: {}", name);
}
});
handle.join().unwrap();
}use dashmap::DashMap;
use std::time::{Duration, Instant};
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();
let mut entry = self.entries.entry(client_id.to_string())
.or_insert(RateLimitEntry {
count: 0,
window_start: now,
});
// Check if window expired
if now.duration_since(entry.window_start) > self.window {
entry.count = 0;
entry.window_start = now;
}
entry.count += 1;
entry.count <= self.max_requests
}
pub fn remaining(&self, client_id: &str) -> u32 {
if let Some(entry) = self.entries.get(client_id) {
if Instant::now().duration_since(entry.window_start) <= self.window {
return self.max_requests.saturating_sub(entry.count);
}
}
self.max_requests
}
pub fn cleanup(&self) {
let now = Instant::now();
self.entries.retain(|_, entry| {
now.duration_since(entry.window_start) <= self.window * 2
});
}
}
fn main() {
let limiter = RateLimiter::new(5, Duration::from_secs(60));
for i in 0..7 {
let allowed = limiter.check("client_123");
let remaining = limiter.remaining("client_123");
println!("Request {}: allowed={}, remaining={}", i + 1, allowed, remaining);
}
}use dashmap::DashMap;
use std::sync::Arc;
pub struct PubSub<T> {
subscribers: DashMap<String, Vec<Box<dyn Fn(&T) + Send + Sync>>>,
}
impl<T> PubSub<T> {
pub fn new() -> Self {
Self {
subscribers: DashMap::new(),
}
}
pub fn subscribe<F>(&self, topic: &str, callback: F)
where
F: Fn(&T) + Send + Sync + 'static,
{
self.subscribers
.entry(topic.to_string())
.or_default()
.push(Box::new(callback));
}
pub fn publish(&self, topic: &str, message: T) {
if let Some(callbacks) = self.subscribers.get(topic) {
for callback in callbacks.iter() {
callback(&message);
}
}
}
pub fn subscriber_count(&self, topic: &str) -> usize {
self.subscribers.get(topic).map(|c| c.len()).unwrap_or(0)
}
}
fn main() {
let pubsub = Arc::new(PubSub::new());
// Subscribe
pubsub.subscribe("news", |msg: &String| {
println!("News subscriber 1: {}", msg);
});
pubsub.subscribe("news", |msg: &String| {
println!("News subscriber 2: {}", msg);
});
println!("Subscribers: {}", pubsub.subscriber_count("news"));
// Publish
pubsub.publish("news", "Hello World!".to_string());
}use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Debug)]
struct Connection {
id: u64,
created_at: Instant,
}
impl Connection {
fn new(id: u64) -> Self {
Self {
id,
created_at: Instant::now(),
}
}
fn is_valid(&self) -> bool {
self.created_at.elapsed() < Duration::from_secs(300)
}
}
pub struct ConnectionPool {
connections: DashMap<String, Connection>,
next_id: std::sync::atomic::AtomicU64,
}
impl ConnectionPool {
pub fn new() -> Self {
Self {
connections: DashMap::new(),
max_size,
next_id: std::sync::atomic::AtomicU64::new(1),
}
}
pub fn get_or_create(&self, name: &str) -> Arc<Connection> {
// Check existing connection
if let Some(conn) = self.connections.get(name) {
if conn.is_valid() {
return Arc::new(Connection::new(conn.id));
}
}
// Create new connection
let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let conn = Connection::new(id);
self.connections.insert(name.to_string(), conn.clone());
Arc::new(conn)
}
pub fn remove(&self, name: &str) {
self.connections.remove(name);
}
pub fn len(&self) -> usize {
self.connections.len()
}
pub fn cleanup_invalid(&self) {
self.connections.retain(|_, conn| conn.is_valid());
}
}
fn main() {
let pool = ConnectionPool::new();
let conn1 = pool.get_or_create("db1");
println!("Connection: {:?}", *conn1);
let conn2 = pool.get_or_create("db1"); // Same connection
println!("Pooled connection: {:?}", *conn2);
println!("Pool size: {}", pool.len());
}use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::Instant;
fn benchmark_dashmap(count: usize) -> Duration {
let map = Arc::new(DashMap::new());
let start = Instant::now();
let handles: Vec<_> = (0..4)
.map(|t| {
let map = Arc::clone(&map);
std::thread::spawn(move || {
for i in 0..count {
let key = t * count + i;
map.insert(key, i);
let _ = map.get(&key);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
start.elapsed()
}
fn benchmark_rwlock(count: usize) -> Duration {
let map = Arc::new(RwLock::new(HashMap::new()));
let start = Instant::now();
let handles: Vec<_> = (0..4)
.map(|t| {
let map = Arc::clone(&map);
std::thread::spawn(move || {
for i in 0..count {
let key = t * count + i;
map.write().unwrap().insert(key, i);
let _ = map.read().unwrap().get(&key);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
start.elapsed()
}
fn main() {
let count = 10000;
let dashmap_time = benchmark_dashmap(count);
let rwlock_time = benchmark_rwlock(count);
println!("DashMap: {:?}", dashmap_time);
println!("RwLock<HashMap>: {:?}", rwlock_time);
println!("Speedup: {:.2}x", rwlock_time.as_secs_f64() / dashmap_time.as_secs_f64());
}use dashmap::DashMap;
use std::collections::HashSet;
fn main() {
let map: DashMap<String, HashSet<i32>> = DashMap::new();
// Add to nested collection
map.entry("set_a".to_string())
.or_insert_with(HashSet::new)
.insert(1);
// Modify nested collection
if let Some(mut set) = map.get_mut(&"set_a") {
set.insert(2);
set.insert(3);
}
// Read nested collection
if let Some(set) = map.get(&"set_a") {
println!("Set contents: {:?}", *set);
}
}DashMap::new() creates a concurrent hash mapmap.insert(key, value) adds entries atomicallymap.get(&key) returns a read guard (lock-free)map.get_mut(&key) returns a write guardmap.entry(key) provides entry API for conditional operationsmap.iter() and map.iter_mut() for safe iterationmap.retain() for filtering entriesArc for sharing across threadsMutex<HashMap> for concurrent workloads