Loading page…
Rust walkthroughs
Loading page…
The dashmap crate provides a blazing fast, thread-safe HashMap implementation in Rust. Unlike std::collections::HashMap wrapped in a Mutex or RwLock, DashMap uses sharded locking (also known as striped locking) to allow concurrent access to different parts of the map simultaneously. This dramatically reduces contention in multi-threaded scenarios. Each shard has its own lock, so operations on different keys often proceed in parallel.
Key features:
std::collections::HashMap with additional methodsentry().or_insert()# Cargo.toml
[dependencies]
dashmap = "5.5"use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let map: Arc<DashMap<i32, String>> = Arc::new(DashMap::new());
let mut handles = vec![];
for i in 0..10 {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
map.insert(i, format!("value-{}", i));
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Map has {} entries", map.len());
if let Some(value) = map.get(&5) {
println!("Key 5: {}", value);
}
}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!("Map: {:?}", map);
println!("Length: {}", map.len());
println!("Is empty: {}", map.is_empty());
// Get values (returns DashMapRef with RAII guard)
if let Some(value) = map.get("one") {
println!("Value for 'one': {}", *value);
}
// Check if contains key
println!("Contains 'two': {}", map.contains_key("two"));
println!("Contains 'four': {}", map.contains_key("four"));
// Remove entry
let removed = map.remove("two");
println!("Removed: {:?}", removed);
println!("Length after remove: {}", map.len());
// Clear map
map.clear();
println!("Is empty after clear: {}", map.is_empty());
}use dashmap::DashMap;
fn main() {
let map: DashMap<&str, i32> = DashMap::new();
// or_insert - insert if missing
map.entry("count").or_insert(0);
println!("After or_insert: {:?}", map.get("count"));
// or_insert_with - insert with closure if missing
map.entry("computed").or_insert_with(|| {
println!("Computing value...");
42
});
println!("Computed: {:?}", map.get("computed"));
// and_modify - modify existing value
map.entry("count").and_modify(|v| *v += 1);
println!("After and_modify: {:?}", map.get("count"));
// or_insert_with handles missing key
map.entry("missing").and_modify(|v| *v += 1).or_insert(100);
println!("Missing key: {:?}", map.get("missing"));
// Counter pattern
let words = ["apple", "banana", "apple", "cherry", "banana", "apple"];
for word in words {
map.entry(word).and_modify(|v| *v += 1).or_insert(1);
}
println!("Word counts:");
for entry in map.iter() {
println!(" {} => {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
fn main() {
let map: DashMap<i32, &str> = DashMap::new();
map.insert(1, "one");
map.insert(2, "two");
map.insert(3, "three");
map.insert(4, "four");
map.insert(5, "five");
// Iterate over entries (holds shard lock briefly)
println!("Iterating:");
for entry in map.iter() {
println!(" {} => {}", entry.key(), entry.value());
}
// Iterate over keys
println!("\nKeys:");
for key in map.iter().map(|e| e.key()) {
println!(" {}", key);
}
// Iterate over values
println!("\nValues:");
for value in map.iter().map(|e| e.value()) {
println!(" {}", value);
}
// Mutable iteration
println!("\nMutable iteration (doubling values):");
for mut entry in map.iter_mut() {
*entry.value_mut() = "modified";
}
for entry in map.iter() {
println!(" {} => {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
fn main() {
let map: DashMap<&str, i32> = DashMap::new();
map.insert("counter", 0);
// Method 1: get + update (requires explicit handling)
// Note: get() returns a reference guard
{
let value = map.get("counter").unwrap();
println!("Current value: {}", *value);
} // Guard dropped here
// Method 2: entry + and_modify
for _ in 0..5 {
map.entry("counter").and_modify(|v| *v += 1);
}
println!("After 5 increments: {:?}", map.get("counter"));
// Method 3: iter_mut for bulk updates
map.insert("a", 10);
map.insert("b", 20);
map.insert("c", 30);
for mut entry in map.iter_mut() {
*entry.value_mut() *= 2;
}
println!("After doubling:");
for entry in map.iter() {
println!(" {} => {}", entry.key(), entry.value());
}
}use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn main() {
let map: Arc<DashMap<i32, i32>> = Arc::new(DashMap::new());
// Initialize with values
for i in 0..100 {
map.insert(i, 0);
}
let mut handles = vec![];
// Spawn writers
for _ in 0..4 {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for i in 0..100 {
map.entry(i).and_modify(|v| *v += 1);
}
}));
}
// Spawn readers
for _ in 0..2 {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for i in 0..100 {
if let Some(value) = map.get(&i) {
// Just read
let _ = *value;
}
}
}));
}
for handle in handles {
handle.join().unwrap();
}
// Verify all counters reached 4
println!("Final values:");
for i in 0..5 {
println!(" key {} => {}", i, *map.get(&i).unwrap());
}
println!("All values are 4: {}", map.iter().all(|e| *e.value() == 4));
}use dashmap::DashMap;
fn main() {
// Default: number of shards = number of CPU cores * 4
let default_map: DashMap<i32, i32> = DashMap::new();
println!("Default shards: {}", default_map.shards().len());
// Custom number of shards (should be power of 2)
let custom_map: DashMap<i32, i32> = DashMap::with_shard_amount(16);
println!("Custom shards: {}", custom_map.shards().len());
// With capacity and shards
let map: DashMap<i32, i32> = DashMap::with_capacity_and_shard_amount(1000, 32);
println!("Capacity map shards: {}", map.shards().len());
// More shards = less contention but more memory overhead
// Fewer shards = more contention but less memory overhead
// Rule of thumb: shards ~= expected concurrent writers
}use dashmap::DashMap;
use std::sync::Arc;
#[derive(Debug, Clone)]
struct User {
id: u32,
name: String,
email: String,
active: bool,
}
fn main() {
let users: DashMap<u32, User> = DashMap::new();
// Insert users
users.insert(1, User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
active: true,
});
users.insert(2, User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
active: false,
});
// Get user
if let Some(user) = users.get(&1) {
println!("User: {} ({})", user.name, user.email);
}
// Update user using entry API
users.entry(2).and_modify(|u| u.active = true);
// Get mutable access
if let Some(mut user) = users.get_mut(&2) {
user.email = "bob_new@example.com".to_string();
}
// Iterate
for entry in users.iter() {
let user = entry.value();
println!("User {}: {} - active: {}", user.id, user.name, user.active);
}
// Filter and collect
let active_users: Vec<_> = users.iter()
.filter(|e| e.value().active)
.map(|e| e.value().clone())
.collect();
println!("Active users: {}", active_users.len());
}use dashmap::DashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::thread;
fn main() {
let map: Arc<DashMap<&str, AtomicUsize>> = Arc::new(DashMap::new());
// Initialize counters
map.insert("requests", AtomicUsize::new(0));
map.insert("errors", AtomicUsize::new(0));
let mut handles = vec![];
for _ in 0..10 {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for _ in 0..1000 {
map.get("requests").unwrap().fetch_add(1, Ordering::SeqCst);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Total requests: {}", map.get("requests").unwrap().load(Ordering::SeqCst));
}use dashmap::DashMap;
fn main() {
let map: DashMap<i32, &str> = DashMap::new();
for i in 0..10 {
map.insert(i, &format!("value-{}", i));
}
// View individual shards
println!("Number of shards: {}", map.shards().len());
for (idx, shard) in map.shards().iter().enumerate() {
let guard = shard.read();
let len = guard.len();
if len > 0 {
println!("Shard {}: {} entries", idx, len);
}
}
// Get shard for a specific key
let shard_idx = map.determine_map(&5);
println!("Key 5 is in shard {}", shard_idx);
// View allows custom operations on shards
map.view(|key, value| {
if *key % 2 == 0 {
println!("Even key {} => {}", key, value);
}
});
}use dashmap::DashMap;
use std::hash::Hash;
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
struct CacheKey {
region: String,
resource_type: String,
id: u64,
}
impl CacheKey {
fn new(region: &str, resource_type: &str, id: u64) -> Self {
Self {
region: region.to_string(),
resource_type: resource_type.to_string(),
id,
}
}
}
#[derive(Debug, Clone)]
struct CacheEntry {
data: String,
created_at: u64,
ttl_seconds: u64,
}
impl CacheEntry {
fn new(data: String, ttl_seconds: u64, now: u64) -> Self {
Self {
data,
created_at: now,
ttl_seconds,
}
}
fn is_expired(&self, now: u64) -> bool {
now > self.created_at + self.ttl_seconds
}
}
fn main() {
let cache: DashMap<CacheKey, CacheEntry> = DashMap::new();
// Insert entries
cache.insert(
CacheKey::new("us-east-1", "user", 1),
CacheEntry::new("user_data_1".to_string(), 3600, 0),
);
cache.insert(
CacheKey::new("us-west-2", "product", 42),
CacheEntry::new("product_data_42".to_string(), 1800, 0),
);
// Look up by key
let key = CacheKey::new("us-east-1", "user", 1);
if let Some(entry) = cache.get(&key) {
println!("Found: {}", entry.data);
}
// Check existence
let other_key = CacheKey::new("eu-west-1", "user", 1);
println!("Exists: {}", cache.contains_key(&other_key));
}use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
#[derive(Clone)]
struct RateLimitEntry {
count: u32,
window_start: Instant,
}
struct RateLimiter {
limits: DashMap<String, RateLimitEntry>,
max_requests: u32,
window_duration: Duration,
}
impl RateLimiter {
fn new(max_requests: u32, window_duration: Duration) -> Self {
Self {
limits: DashMap::new(),
max_requests,
window_duration,
}
}
fn check_rate_limit(&self, client_id: &str) -> bool {
let now = Instant::now();
// Try to get existing entry
if let Some(mut entry) = self.limits.get_mut(client_id) {
let elapsed = now.duration_since(entry.window_start);
if elapsed > self.window_duration {
// Reset window
entry.count = 1;
entry.window_start = now;
true
} else if entry.count < self.max_requests {
entry.count += 1;
true
} else {
false
}
} else {
// New entry
self.limits.insert(client_id.to_string(), RateLimitEntry {
count: 1,
window_start: now,
});
true
}
}
fn cleanup_expired(&self) {
let now = Instant::now();
let expired: Vec<_> = self.limits.iter()
.filter(|e| now.duration_since(e.value().window_start) > self.window_duration)
.map(|e| e.key().clone())
.collect();
for key in expired {
self.limits.remove(&key);
}
}
fn active_clients(&self) -> usize {
self.limits.len()
}
}
fn main() {
let limiter = Arc::new(RateLimiter::new(5, Duration::from_secs(60)));
// Simulate requests
for i in 0..10 {
let allowed = limiter.check_rate_limit("client-1");
println!("Request {}: allowed = {}", i + 1, allowed);
}
// Different client
println!("\nClient 2:");
for i in 0..3 {
let allowed = limiter.check_rate_limit("client-2");
println!("Request {}: allowed = {}", i + 1, allowed);
}
println!("\nActive clients: {}", limiter.active_clients());
}use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use std::hash::Hash;
#[derive(Clone)]
struct CacheEntry<T> {
value: T,
expires_at: Instant,
}
impl<T> CacheEntry<T> {
fn new(value: T, ttl: Duration) -> Self {
Self {
value,
expires_at: Instant::now() + ttl,
}
}
fn is_expired(&self) -> bool {
Instant::now() > self.expires_at
}
}
struct InMemoryCache<K, V> {
store: DashMap<K, CacheEntry<V>>,
default_ttl: Duration,
}
impl<K, V> InMemoryCache<K, V>
where
K: Eq + Hash + Clone,
V: Clone,
{
fn new(default_ttl: Duration) -> Self {
Self {
store: DashMap::new(),
default_ttl,
}
}
fn with_capacity(default_ttl: Duration, capacity: usize) -> Self {
Self {
store: DashMap::with_capacity(capacity),
default_ttl,
}
}
fn get(&self, key: &K) -> Option<V> {
self.store.get(key).and_then(|entry| {
if entry.is_expired() {
None
} else {
Some(entry.value.clone())
}
})
}
fn set(&self, key: K, value: V) {
self.set_with_ttl(key, value, self.default_ttl);
}
fn set_with_ttl(&self, key: K, value: V, ttl: Duration) {
let entry = CacheEntry::new(value, ttl);
self.store.insert(key, entry);
}
fn delete(&self, key: &K) -> Option<V> {
self.store.remove(key).map(|(_, entry)| entry.value)
}
fn contains(&self, key: &K) -> bool {
self.store.get(key).map(|e| !e.is_expired()).unwrap_or(false)
}
fn cleanup_expired(&self) -> usize {
let expired: Vec<_> = self.store.iter()
.filter(|e| e.value().is_expired())
.map(|e| e.key().clone())
.collect();
let count = expired.len();
for key in expired {
self.store.remove(&key);
}
count
}
fn len(&self) -> usize {
self.store.len()
}
}
fn main() {
let cache: InMemoryCache<String, String> = InMemoryCache::new(Duration::from_secs(60));
// Set values
cache.set("user:1".to_string(), "Alice".to_string());
cache.set("user:2".to_string(), "Bob".to_string());
cache.set_with_ttl("temp".to_string(), "data".to_string(), Duration::from_millis(10));
// Get values
if let Some(name) = cache.get(&"user:1".to_string()) {
println!("User 1: {}", name);
}
println!("Contains user:2: {}", cache.contains(&"user:2".to_string()));
// Wait for temp to expire
std::thread::sleep(Duration::from_millis(20));
println!("Contains temp after expiry: {}", cache.contains(&"temp".to_string()));
// Cleanup
let removed = cache.cleanup_expired();
println!("Expired entries removed: {}", removed);
println!("Cache size: {}", cache.len());
}use dashmap::DashMap;
use std::sync::Arc;
use std::collections::VecDeque;
struct Connection {
id: u64,
endpoint: String,
}
impl Connection {
fn new(id: u64, endpoint: &str) -> Self {
Self {
id,
endpoint: endpoint.to_string(),
}
}
fn is_healthy(&self) -> bool {
// Simulate health check
true
}
}
struct ConnectionPool {
pools: DashMap<String, VecDeque<Connection>>,
max_per_endpoint: usize,
next_id: std::sync::atomic::AtomicU64,
}
impl ConnectionPool {
fn new(max_per_endpoint: usize) -> Self {
Self {
pools: DashMap::new(),
max_per_endpoint,
next_id: std::sync::atomic::AtomicU64::new(1),
}
}
fn get(&self, endpoint: &str) -> Option<Connection> {
self.pools.get_mut(endpoint).and_then(|mut pool| {
pool.pop_front()
})
}
fn return_connection(&self, endpoint: &str, conn: Connection) {
self.pools.entry(endpoint.to_string())
.or_insert_with(VecDeque::new)
.push_back(conn);
}
fn create_if_needed(&self, endpoint: &str) -> Connection {
let id = self.next_id.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Connection::new(id, endpoint)
}
fn pool_size(&self, endpoint: &str) -> usize {
self.pools.get(endpoint).map(|p| p.len()).unwrap_or(0)
}
fn total_connections(&self) -> usize {
self.pools.iter().map(|e| e.value().len()).sum()
}
}
fn main() {
let pool = Arc::new(ConnectionPool::new(10));
// Simulate getting connections
let conn1 = pool.get("api.example.com").unwrap_or_else(|| {
pool.create_if_needed("api.example.com")
});
println!("Got connection {}", conn1.id);
// Return connection
pool.return_connection("api.example.com", conn1);
println!("Pool size: {}", pool.pool_size("api.example.com"));
// Get again (should reuse)
let conn2 = pool.get("api.example.com").unwrap();
println!("Reused connection {}", conn2.id);
// Different endpoint
let conn3 = pool.create_if_needed("db.example.com");
pool.return_connection("db.example.com", conn3);
println!("Total connections: {}", pool.total_connections());
}use dashmap::DashMap;
use std::collections::HashMap;
use std::sync::{Arc, Mutex, RwLock};
use std::thread;
use std::time::Instant;
fn main() {
let iterations = 100_000;
let threads = 8;
// DashMap
{
let map: Arc<DashMap<i32, i32>> = Arc::new(DashMap::new());
for i in 0..threads * 10 {
map.insert(i, 0);
}
let start = Instant::now();
let mut handles = vec![];
for _ in 0..threads {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for i in 0..iterations {
map.entry(i % (threads * 10)).and_modify(|v| *v += 1);
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("DashMap: {:?}", start.elapsed());
}
// Mutex<HashMap>
{
let map: Arc<Mutex<HashMap<i32, i32>>> = Arc::new(Mutex::new(HashMap::new()));
for i in 0..threads * 10 {
map.lock().unwrap().insert(i, 0);
}
let start = Instant::now();
let mut handles = vec![];
for _ in 0..threads {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for i in 0..iterations {
let mut map = map.lock().unwrap();
*map.entry(i % (threads * 10)).or_insert(0) += 1;
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("Mutex<HashMap>: {:?}", start.elapsed());
}
// RwLock<HashMap>
{
let map: Arc<RwLock<HashMap<i32, i32>>> = Arc::new(RwLock::new(HashMap::new()));
for i in 0..threads * 10 {
map.write().unwrap().insert(i, 0);
}
let start = Instant::now();
let mut handles = vec![];
for _ in 0..threads {
let map = Arc::clone(&map);
handles.push(thread::spawn(move || {
for i in 0..iterations {
let mut map = map.write().unwrap();
*map.entry(i % (threads * 10)).or_insert(0) += 1;
}
}));
}
for handle in handles {
handle.join().unwrap();
}
println!("RwLock<HashMap>: {:?}", start.elapsed());
}
// DashMap typically outperforms Mutex<HashMap> and RwLock<HashMap>
// in high-contention scenarios due to sharded locking
}use dashmap::DashMap;
use std::sync::Arc;
use std::hash::Hash;
struct RequestDeduplicator<T> {
pending: DashMap<T, Arc<std::sync::Condvar>>,
}
impl<T> RequestDeduplicator<T>
where
T: Eq + Hash + Clone,
{
fn new() -> Self {
Self {
pending: DashMap::new(),
}
}
fn deduplicate<F, R>(&self, key: T, compute: F) -> R
where
F: FnOnce() -> R,
R: Clone,
{
// Check if already pending
if let Some(_) = self.pending.get(&key) {
// In real code, you'd wait for the result
// This is simplified
println!("Request already in progress for key: {:?}", key);
return compute();
}
// Mark as pending
let condvar = Arc::new(std::sync::Condvar::new());
self.pending.insert(key.clone(), Arc::clone(&condvar));
// Compute result
let result = compute();
// Remove from pending
self.pending.remove(&key);
condvar.notify_all();
result
}
fn pending_count(&self) -> usize {
self.pending.len()
}
}
fn main() {
let dedup: RequestDeduplicator<String> = RequestDeduplicator::new();
let result1 = dedup.deduplicate("user:1".to_string(), || {
println!("Computing for user:1");
"Alice"
});
println!("Result 1: {}", result1);
println!("Pending: {}", dedup.pending_count());
}DashMap::new(), DashMap::with_capacity(), or DashMap::with_shard_amount()insert(), get(), remove() for basic operations (same as HashMap)get_mut() for mutable access to values (returns RAII guard)entry().or_insert() and entry().and_modify() for atomic updatesiter() (read-only) or iter_mut() (mutable)Arc for sharing across threadsshards() to inspect internal shards for debuggingMutex<HashMap> or RwLock<HashMap> in high-contention scenarios