Loading pageā¦
Rust walkthroughs
Loading pageā¦
dashmap::DashMap::entry API compare to std::collections::HashMap::entry for atomic updates?std::collections::HashMap::entry provides a single-threaded API for atomic get-or-insert operations within the context of exclusive access to the map. dashmap::DashMap::entry provides a similar API but operates within a sharded concurrent map where each entry operation only locks the relevant shard rather than the entire map. The key difference is that DashMap::entry enables concurrent access from multiple threads while still providing atomic updates to individual keys, whereas HashMap::entry requires wrapping the entire map in a Mutex or RwLock for concurrent access, which blocks all operations during the entry manipulation. However, DashMap::entry returns a guard that holds the shard lock, requiring careful attention to scope to avoid holding locks across await points or long computations.
use std::collections::HashMap;
fn hashmap_entry_basic() {
let mut map = HashMap::new();
// Insert if key doesn't exist
map.entry("apple").or_insert(5);
map.entry("apple").or_insert(10); // Doesn't overwrite
assert_eq!(map.get("apple"), Some(&5));
// Modify if exists
map.entry("apple").and_modify(|count| *count += 1).or_insert(1);
assert_eq!(map.get("apple"), Some(&6));
// Get or insert with computation
let value = map.entry("banana").or_insert_with(|| {
println!("Computing value for banana");
42
});
assert_eq!(value, &42);
}HashMap::entry provides atomic single-key operations without any concern for concurrent access.
use dashmap::DashMap;
fn dashmap_entry_basic() {
let map = DashMap::new();
// Same entry API, but thread-safe
map.entry("apple").or_insert(5);
map.entry("apple").or_insert(10); // Doesn't overwrite
assert_eq!(map.get("apple").map(|r| *r), Some(5));
// Modify if exists
map.entry("apple").and_modify(|count| *count += 1).or_insert(1);
assert_eq!(map.get("apple").map(|r| *r), Some(6));
// Get or insert with computation
let value = map.entry("banana").or_insert_with(|| {
println!("Computing value for banana");
42
});
assert_eq!(*value, 42);
}DashMap::entry provides the same API with concurrent access support.
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
fn hashmap_concurrent() {
let map = Arc::new(Mutex::new(HashMap::new()));
// Thread 1
let map1 = map.clone();
let h1 = std::thread::spawn(move || {
let mut map = map1.lock().unwrap();
map.entry("key").or_insert(0);
map.entry("key").and_modify(|v| *v += 1);
// Lock held during entire operation
});
// Thread 2
let map2 = map.clone();
let h2 = std::thread::spawn(move || {
let mut map = map2.lock().unwrap();
// Must wait for Thread 1 to release lock
map.entry("key").or_insert(0);
map.entry("key").and_modify(|v| *v += 10);
});
h1.join().unwrap();
h2.join().unwrap();
let map = map.lock().unwrap();
println!("Final value: {:?}", map.get("key"));
}HashMap with Mutex blocks all threads during any entry operation on the entire map.
use dashmap::DashMap;
use std::sync::Arc;
fn dashmap_concurrent() {
let map = Arc::new(DashMap::new());
// Thread 1 - accesses "key1"
let map1 = map.clone();
let h1 = std::thread::spawn(move || {
// Only locks the shard containing "key1"
map1.entry("key1").or_insert(0);
map1.entry("key1").and_modify(|v| *v += 1);
});
// Thread 2 - accesses "key2"
let map2 = map.clone();
let h2 = std::thread::spawn(move || {
// Only locks the shard containing "key2"
// Can run concurrently if "key1" and "key2" are in different shards
map2.entry("key2").or_insert(0);
map2.entry("key2").and_modify(|v| *v += 10);
});
// Thread 3 - also accesses "key1" (same shard as Thread 1)
let map3 = map.clone();
let h3 = std::thread::spawn(move || {
// Must wait if same shard as Thread 1
map3.entry("key1").and_modify(|v| *v += 100);
});
h1.join().unwrap();
h2.join().unwrap();
h3.join().unwrap();
println!("key1: {:?}", map.get("key1").map(|r| *r)); // 101
println!("key2: {:?}", map.get("key2").map(|r| *r)); // 10
}DashMap uses sharded locking: different keys can be accessed concurrently if they're in different shards.
use dashmap::DashMap;
use std::sync::Arc;
fn dashmap_entry_guards() {
let map = Arc::new(DashMap::new());
map.insert("key", 0);
// The entry() call returns an OccupiedEntry that holds the shard lock
let entry = map.entry("key");
// Shard is locked here - other operations on keys in same shard wait
// This is similar to HashMap's Mutex, but only for one shard
// The lock is released when entry goes out of scope
{
let mut entry = map.entry("key").or_insert(0);
*entry += 1;
// Lock still held while we use `entry`
}
// Lock released here
// Pattern: release lock quickly
map.entry("key").and_modify(|v| {
// Quick modification while holding lock
*v += 1;
});
// Lock released immediately after and_modify
}
fn dangerous_lock_patterns() {
let map = Arc::new(DashMap::new());
// DANGEROUS: Don't hold entry while doing slow operations
if let dashmap::mapref::entry::Entry::Occupied(mut entry) = map.entry("key") {
let value = entry.get_mut();
// Don't do slow I/O here - shard is locked!
// std::thread::sleep(std::time::Duration::from_secs(1));
*value += 1;
}
// BETTER: Release lock, then do slow work
let current = map.entry("key").or_insert(0).clone();
// Lock released - now do slow work
let new_value = expensive_computation(current);
// Acquire lock again to update
map.insert("key", new_value);
}
fn expensive_computation(n: i32) -> i32 {
n + 1
}DashMap::entry holds a shard lock; release it quickly to maximize concurrency.
use std::collections::HashMap;
use dashmap::DashMap;
fn or_insert_comparison() {
// HashMap: single-threaded, no contention
let mut hashmap = HashMap::new();
hashmap.entry("key").or_insert(vec![1, 2, 3]);
hashmap.entry("key").or_insert(vec![4, 5, 6]); // Not inserted
assert_eq!(hashmap.get("key"), Some(&vec![1, 2, 3]));
// DashMap: concurrent-safe, same semantics
let dashmap = DashMap::new();
dashmap.entry("key").or_insert(vec![1, 2, 3]);
dashmap.entry("key").or_insert(vec![4, 5, 6]); // Not inserted
assert_eq!(dashmap.get("key").map(|r| r.clone()), Some(vec![1, 2, 3]));
}
fn or_insert_with_comparison() {
// HashMap: lazy initialization
let mut hashmap = HashMap::new();
let mut called = false;
hashmap.entry("key").or_insert_with(|| {
called = true;
42
});
assert!(called);
called = false;
hashmap.entry("key").or_insert_with(|| {
called = true;
100
});
assert!(!called); // Not called - key exists
// DashMap: same lazy initialization, but thread-safe
let dashmap = DashMap::new();
let mut called = false;
dashmap.entry("key").or_insert_with(|| {
called = true;
42
});
assert!(called);
}Both provide identical semantics; DashMap adds thread safety.
use std::collections::HashMap;
use dashmap::DashMap;
fn and_modify_comparison() {
// HashMap: modify existing value
let mut hashmap = HashMap::new();
hashmap.insert("counter", 0);
hashmap.entry("counter").and_modify(|v| *v += 1);
assert_eq!(hashmap.get("counter"), Some(&1));
// and_modify on missing key does nothing
hashmap.entry("missing").and_modify(|v| *v += 1);
assert!(!hashmap.contains_key("missing"));
// DashMap: same behavior, concurrent-safe
let dashmap = DashMap::new();
dashmap.insert("counter", 0);
dashmap.entry("counter").and_modify(|v| *v += 1);
assert_eq!(dashmap.get("counter").map(|r| *r), Some(1));
}
fn and_modify_or_insert_pattern() {
// Common pattern: increment or initialize
let mut hashmap = HashMap::new();
hashmap.entry("counter")
.and_modify(|v| *v += 1)
.or_insert(1);
let dashmap = DashMap::new();
dashmap.entry("counter")
.and_modify(|v| *v += 1)
.or_insert(1);
}The and_modify pattern works identically in both APIs.
use std::collections::HashMap;
use dashmap::DashMap;
fn entry_variants_hashmap() {
let mut map = HashMap::new();
match map.entry("key") {
std::collections::hash_map::Entry::Occupied(entry) => {
println!("Key exists: {}", entry.key());
}
std::collections::hash_map::Entry::Vacant(entry) => {
println!("Key missing, inserting default");
entry.insert(42);
}
}
}
fn entry_variants_dashmap() {
let map = DashMap::new();
match map.entry("key") {
dashmap::mapref::entry::Entry::Occupied(entry) => {
println!("Key exists: {}", entry.key());
// entry holds shard lock
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
println!("Key missing, inserting default");
entry.insert(42);
// entry holds shard lock
}
}
// Shard lock released when entry goes out of scope
}Both use the same Occupied/Vacant pattern; DashMap variants hold locks.
use dashmap::DashMap;
use std::sync::Arc;
fn concurrent_counter() {
let counts = Arc::new(DashMap::new());
let handles: Vec<_> = (0..100)
.map(|i| {
let counts = counts.clone();
std::thread::spawn(move || {
let key = format!("item_{}", i % 10); // 10 different keys
counts.entry(key)
.and_modify(|v| *v += 1)
.or_insert(1);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
// Each key should have count of 10
for i in 0..10 {
let key = format!("item_{}", i);
assert_eq!(counts.get(&key).map(|r| *r), Some(10));
}
}
// Compare with HashMap + Mutex
fn hashmap_mutex_counter() {
use std::collections::HashMap;
use std::sync::Mutex;
let counts = Arc::new(Mutex::new(HashMap::new()));
let handles: Vec<_> = (0..100)
.map(|i| {
let counts = counts.clone();
std::thread::spawn(move || {
let mut map = counts.lock().unwrap();
let key = format!("item_{}", i % 10);
map.entry(key)
.and_modify(|v| *v += 1)
.or_insert(1);
// Lock held for entire operation
})
})
.collect();
for h in handles {
h.join().unwrap();
}
}DashMap allows concurrent updates to different keys; HashMap + Mutex serializes all updates.
use dashmap::DashMap;
use std::sync::Arc;
use std::time::Duration;
use std::thread;
fn cache_population() {
let cache = Arc::new(DashMap::new());
// Multiple threads populating cache
let handles: Vec<_> = (0..10)
.map(|thread_id| {
let cache = cache.clone();
thread::spawn(move || {
for i in 0..10 {
let key = format!("key_{}", thread_id * 10 + i);
// Only compute if not present
cache.entry(key.clone())
.or_insert_with(|| {
// Expensive computation
let value = expensive_lookup(&key);
value
});
}
})
})
.collect();
for h in handles {
h.join().unwrap();
}
println!("Cache size: {}", cache.len());
}
fn expensive_lookup(key: &str) -> String {
// Simulate expensive lookup
format!("value_for_{}", key)
}
// Key insight: or_insert_with is only called if key is missing
// The computation happens while holding the shard lock
// For very expensive computations, consider double-check patternFor expensive computations in or_insert_with, the shard lock is held during computation.
use dashmap::DashMap;
use std::sync::Arc;
fn expensive_computation_cache() {
let cache = Arc::new(DashMap::new());
// Problem: or_insert_with holds lock during computation
cache.entry("key").or_insert_with(|| {
// This holds the shard lock during expensive computation
// Other threads accessing same shard are blocked
very_expensive_computation()
});
// Better: double-check pattern to minimize lock time
let cache2 = Arc::new(DashMap::new());
// First check without locking
if let Some(value) = cache2.get("key") {
// Already cached
let _ = value;
} else {
// Not cached, compute outside of lock
let computed = very_expensive_computation();
// Now try to insert
cache2.entry("key")
.or_insert_with(|| {
// Another thread might have inserted by now
// Return our computed value, but it might not be used
computed.clone()
});
}
// Even better: use the entry pattern carefully
fn get_or_compute(cache: &DashMap<String, String>, key: &str) -> String {
// Check if exists
if let Some(value) = cache.get(key) {
return value.clone();
}
// Compute outside lock
let value = very_expensive_computation();
// Insert - might race with another thread
cache.entry(key.to_string())
.or_insert_with(|| value.clone())
.clone()
}
}
fn very_expensive_computation() -> String {
"expensive_result".to_string()
}For expensive computations, consider patterns that minimize time holding the shard lock.
use dashmap::DashMap;
fn dashmap_entry_limitations() {
let map = DashMap::new();
map.insert("a", 1);
map.insert("b", 2);
// Cannot hold two entries at once (might deadlock on same shard)
// This would be dangerous:
// let entry_a = map.entry("a");
// let entry_b = map.entry("b"); // Might deadlock if same shard
// Safe: complete one entry operation before another
{
let mut entry = map.entry("a").or_insert(0);
*entry += 1;
} // Entry dropped, lock released
{
let mut entry = map.entry("b").or_insert(0);
*entry += 1;
}
// HashMap with Mutex has the same limitation
// but at the whole-map level instead of shard level
}
// Deadlock risk demonstration
fn potential_deadlock() {
use std::sync::Arc;
let map = Arc::new(DashMap::new());
// If these keys hash to the same shard, this could deadlock
// in a multi-threaded scenario where threads try to acquire
// entries in different orders
// Thread 1
let map1 = map.clone();
let h1 = std::thread::spawn(move || {
// Hold entry for "a"
let _e1 = map1.entry("a").or_insert(0);
// Try to get entry for "b"
// If same shard, we're trying to lock twice - this works
// in DashMap because it's reentrant for the same thread
let _e2 = map1.entry("b").or_insert(0);
});
h1.join().unwrap();
}Be careful about holding multiple entries simultaneously; they might lock the same shard.
use dashmap::DashMap;
use std::collections::HashMap;
// HashMap: easy to make atomic multi-key operations
fn hashmap_multi_key_atomic() {
let mut map = HashMap::new();
// Single lock protects entire operation
map.entry("a").or_insert(0);
map.entry("b").or_insert(0);
// Both entries modified atomically (single-threaded)
if let Some(a) = map.get_mut("a") {
*a += 1;
}
if let Some(b) = map.get_mut("b") {
*b += 1;
}
}
// DashMap: multi-key operations are NOT atomic across keys
fn dashmap_multi_key_not_atomic() {
let map = DashMap::new();
// Each entry operation locks a different shard
map.entry("a").or_insert(0); // Locks shard for "a"
map.entry("b").or_insert(0); // Locks shard for "b" (maybe different)
// These are NOT atomic together
// Another thread could see "a" updated but "b" not yet updated
// For atomic multi-key operations, you need external synchronization
}
// If you need atomic multi-key operations with DashMap
fn dashmap_with_external_sync() {
use std::sync::Mutex;
let map = DashMap::new();
let multi_key_lock = Mutex::new(());
// Lock for multi-key operation
let _guard = multi_key_lock.lock().unwrap();
// Now safe to do multi-key operations
map.entry("a").and_modify(|v| *v += 1);
map.entry("b").and_modify(|v| *v += 1);
// But this reduces concurrency for those keys
}DashMap doesn't provide atomic multi-key operations; use external synchronization if needed.
| Aspect | HashMap::entry | DashMap::entry |
|--------|------------------|------------------|
| Thread safety | None (single-threaded) | Sharded locking |
| Concurrency | N/A (single-threaded) | Concurrent access to different shards |
| Lock granularity | N/A | Per-shard |
| Lock scope | N/A | During entry operation |
| Multi-key atomicity | Yes (single lock) | No (different shards) |
| Memory overhead | Lower | Higher (shards + overhead) |
| API similarity | Standard | Same API, concurrent semantics |
The DashMap::entry API provides the same ergonomic interface as HashMap::entry but with concurrent access semantics that enable multi-threaded usage without a global lock:
Key similarities:
or_insert, or_insert_with, and_modify methodsOccupied/Vacant entry variantsKey differences:
DashMap::entry holds a shard lock; HashMap::entry has no locking concernDashMap allows concurrent access to different keys; HashMap + Mutex blocks all accessDashMap cannot provide atomic multi-key operations; HashMap naturally doesDashMap entry guards must be released quickly; HashMap has no such concernWhen to use DashMap::entry:
When to use HashMap::entry:
Mutex<HashMap> when all updates need global synchronizationKey insight: DashMap::entry is not a drop-in replacement for HashMap::entry in concurrent codeāit's better. A Mutex<HashMap> blocks all threads on any entry operation, while DashMap only blocks threads accessing the same shard. However, this finer granularity means you cannot atomically modify multiple keys; each entry operation is atomic only for that single key. Choose based on whether you need single-key or multi-key atomicity.