Loading page…
Rust walkthroughs
Loading page…
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:
Mutex<Map> wrapper# 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);
}
}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());
}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());
}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());
}
}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"));
}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<_>>());
}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());
}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"));
}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
}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);
}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);
}
}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
}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);
}
}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());
}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());
}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);
}
}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);
}
}
}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>());
}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);
}DashMap::new() creates a concurrent hash map with default shardingDashMap::with_shards(n) creates with custom shard count (power of 2)map.insert(k, v) and map.get(&k) for basic operationsmap.get(&k) returns Option<Ref<K, V>> — a lock guardmap.get_mut(&k) returns Option<RefMut<K, V>> for mutationmap.entry(k) provides the familiar entry APImap.iter() and map.iter_mut() for iterationmap.retain(predicate) for filtering entriesMutex<Map> wrapper needed