Loading page…
Rust walkthroughs
Loading page…
dashmap::DashMap::entry and DashMap::get_mut for conditional insertion patterns?The DashMap::entry and DashMap::get_mut methods provide fundamentally different approaches to conditional map operations in concurrent hash maps. get_mut returns a reference to an existing value if the key exists, requiring separate lookup and insert operations for conditional insertion patterns. entry provides atomic conditional access through the Entry API, allowing you to check for existence and potentially insert in a single atomic operation without holding a lock while computing the value. The key difference is that entry enables compute-on-absent patterns efficiently—you can compute a value only when needed, avoiding unnecessary computation and potential deadlocks from holding locks during expensive operations.
use dashmap::DashMap;
use std::sync::Arc;
fn basic_operations() {
let map: DashMap<String, i32> = DashMap::new();
// Basic insert
map.insert("key1".to_string(), 42);
// Basic get (returns reference wrapper)
if let Some(ref_entry) = map.get("key1") {
println!("Value: {}", *ref_entry.value());
}
// get_mut returns mutable reference wrapper
if let Some(mut ref_entry) = map.get_mut("key1") {
*ref_entry.value_mut() += 1;
}
println!("After mutation: {}", map.get("key1").unwrap().value());
}DashMap provides concurrent access with fine-grained locking at the shard level.
use dashmap::DashMap;
fn get_mut_pattern(map: &DashMap<String, Vec<i32>>, key: &str, value: i32) {
// get_mut: modify existing value if key exists
if let Some(mut entry) = map.get_mut(key) {
entry.value_mut().push(value);
} else {
// Key doesn't exist, need separate insert
map.insert(key.to_string(), vec![value]);
}
}
fn demonstrate_get_mut() {
let map: DashMap<String, Vec<i32>> = DashMap::new();
get_mut_pattern(&map, "numbers", 1);
get_mut_pattern(&map, "numbers", 2);
get_mut_pattern(&map, "numbers", 3);
// Problem: Two separate operations (get_mut + insert)
// Between get_mut returning None and insert, another thread could insert
}get_mut requires two separate operations for conditional insertion.
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn race_condition_demonstration() {
let map: Arc<DashMap<String, i32>> = Arc::new(DashMap::new());
let key = "counter";
// Multiple threads trying to initialize if not present
let handles: Vec<_> = (0..5).map(|i| {
let map = Arc::clone(&map);
thread::spawn(move || {
// Problem: check and insert are not atomic
if map.get_mut(key).is_none() {
// Another thread could have inserted here
// This thread would overwrite their value
map.insert(key.to_string(), i);
}
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// Final value is unpredictable - last writer wins
println!("Final value: {:?}", map.get(key));
}The check-then-insert pattern with get_mut has a race condition.
use dashmap::DashMap;
fn entry_pattern(map: &DashMap<String, Vec<i32>>, key: &str, value: i32) {
use dashmap::mapref::entry::Entry;
// entry provides atomic check-and-insert
match map.entry(key.to_string()) {
Entry::Occupied(mut entry) => {
// Key exists, modify existing value
entry.get_mut().push(value);
}
Entry::Vacant(entry) => {
// Key doesn't exist, insert new value
entry.insert(vec![value]);
}
}
}
fn demonstrate_entry() {
let map: DashMap<String, Vec<i32>> = DashMap::new();
entry_pattern(&map, "numbers", 1);
entry_pattern(&map, "numbers", 2);
entry_pattern(&map, "numbers", 3);
// Single atomic operation - no race condition
assert_eq!(map.get("numbers").unwrap().value(), &[1, 2, 3]);
}entry combines the check and insert into a single atomic operation.
use dashmap::DashMap;
fn compute_on_absent(map: &DashMap<String, Vec<i32>>, key: &str) -> Vec<i32> {
// or_insert_with only computes when key is absent
map.entry(key.to_string())
.or_insert_with(|| {
// This closure only runs if key doesn't exist
println!("Computing initial value for {}", key);
vec![1, 2, 3]
})
.value()
.clone()
}
fn expensive_computation_example() {
let map: DashMap<String, String> = DashMap::new();
// Expensive computation only happens once
let value = map.entry("key".to_string())
.or_insert_with(|| {
// Simulate expensive computation
std::thread::sleep(std::time::Duration::from_millis(100));
"computed_value".to_string()
});
// Second call skips the computation
let value2 = map.entry("key".to_string())
.or_insert_with(|| {
panic!("Should not be called!");
});
}or_insert_with computes the default value lazily, only when needed.
use dashmap::DashMap;
// Pattern 1: get_mut with explicit insert (two operations)
fn get_mut_insert(map: &DashMap<String, i32>, key: &str) -> i32 {
if let Some(mut entry) = map.get_mut(key) {
*entry.value_mut() += 1;
*entry.value()
} else {
map.insert(key.to_string(), 0);
0
}
}
// Pattern 2: entry with or_insert_with (single operation)
fn entry_insert(map: &DashMap<String, i32>, key: &str) -> i32 {
let mut entry = map.entry(key.to_string()).or_insert(0);
*entry.value_mut() += 1;
*entry.value()
}
// Pattern 3: entry with explicit match (maximum control)
fn entry_match(map: &DashMap<String, i32>, key: &str) -> i32 {
use dashmap::mapref::entry::Entry;
match map.entry(key.to_string()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() += 1;
*entry.get()
}
Entry::Vacant(entry) => {
entry.insert(1);
1
}
}
}Each pattern has different atomicity and control characteristics.
use dashmap::DashMap;
use std::time::Duration;
fn lock_behavior_comparison() {
let map: DashMap<String, String> = DashMap::new();
// get_mut: holds lock during computation
if let Some(mut entry) = map.get_mut("key") {
// Lock held during expensive operation
std::thread::sleep(Duration::from_millis(100));
*entry.value_mut() = "modified".to_string();
}
// entry with or_insert: computes before acquiring write lock
map.entry("key".to_string())
.or_insert_with(|| {
// Computation happens before insert
// No write lock held during computation
"computed".to_string()
});
// or_insert_with avoids holding lock during expensive closure
let expensive_value = map.entry("expensive".to_string())
.or_insert_with(|| {
// This computation is NOT under lock
std::thread::sleep(Duration::from_millis(100));
"expensive_result".to_string()
});
}entry computes default values before acquiring locks.
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn concurrent_entry_operations() {
let map: Arc<DashMap<String, u64>> = Arc::new(DashMap::new());
let handles: Vec<_> = (0..10).map(|i| {
let map = Arc::clone(&map);
thread::spawn(move || {
let key = format!("counter-{}", i % 3);
// Atomic increment: check existence and initialize if needed
map.entry(key)
.and_modify(|count| *count += 1)
.or_insert(1);
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// Each counter should have correct total
println!("counter-0: {}", map.get("counter-0").unwrap().value());
println!("counter-1: {}", map.get("counter-1").unwrap().value());
println!("counter-2: {}", map.get("counter-2").unwrap().value());
}entry with and_modify enables atomic read-modify-write operations.
use dashmap::DashMap;
use dashmap::mapref::entry::Entry;
fn entry_api_methods() {
let map: DashMap<String, i32> = DashMap::new();
// or_insert: insert default if absent
let entry = map.entry("a".to_string()).or_insert(42);
assert_eq!(*entry.value(), 42);
// or_insert_with: lazily compute default if absent
let entry = map.entry("b".to_string()).or_insert_with(|| {
// Only called if "b" doesn't exist
100
});
assert_eq!(*entry.value(), 100);
// or_default: insert Default::default() if absent
let entry = map.entry("c".to_string()).or_default();
assert_eq!(*entry.value(), 0);
// and_modify: modify existing value
map.entry("a".to_string())
.and_modify(|v| *v += 1)
.or_insert(0);
assert_eq!(*map.get("a").unwrap().value(), 43);
// or_insert_with and_modify combined
map.entry("d".to_string())
.and_modify(|v| *v *= 2)
.or_insert_with(|| 10);
// "d" didn't exist, so or_insert_with runs (and_modify is skipped)
assert_eq!(*map.get("d").unwrap().value(), 10);
}The Entry API provides composable methods for conditional operations.
use dashmap::DashMap;
use std::collections::HashMap;
fn complex_nested_pattern() {
let map: DashMap<String, HashMap<String, Vec<i32>>> = DashMap::new();
// Insert nested structure atomically
map.entry("outer".to_string())
.or_insert_with(HashMap::new)
.value_mut()
.entry("inner".to_string())
.or_insert_with(Vec::new)
.push(42);
// Multiple modifications in sequence
map.entry("outer".to_string())
.and_modify(|outer| {
outer.entry("inner".to_string())
.or_insert_with(Vec::new)
.push(43);
})
.or_insert_with(|| {
let mut inner = HashMap::new();
inner.insert("inner".to_string(), vec![43]);
inner
});
}Entry methods chain naturally for complex nested structures.
use dashmap::DashMap;
fn when_get_mut_is_appropriate() {
let map: DashMap<String, Vec<i32>> = DashMap::new();
map.insert("key".to_string(), vec![1, 2, 3]);
// get_mut is appropriate when you KNOW the key exists
// and just want to modify in place
// Simple in-place modification
if let Some(mut entry) = map.get_mut("key") {
entry.value_mut().push(4);
entry.value_mut().push(5);
}
// Multiple sequential modifications
if let Some(mut entry) = map.get_mut("key") {
let v = entry.value_mut();
v.sort();
v.dedup();
v.reverse();
}
// get_mut is simpler than entry when you don't need insertion
}Use get_mut when you're certain the key exists and only need mutation.
use dashmap::DashMap;
use std::time::Instant;
fn benchmark_patterns() {
let map: DashMap<String, i32> = DashMap::new();
let iterations = 1_000_000;
// Pattern 1: get_mut + insert
let start = Instant::now();
for i in 0..iterations {
let key = format!("key-{}", i % 100);
if map.get_mut(&key).is_none() {
map.insert(key, 0);
}
}
println!("get_mut + insert: {:?}", start.elapsed());
map.clear();
// Pattern 2: entry + or_insert
let start = Instant::now();
for i in 0..iterations {
let key = format!("key-{}", i % 100);
map.entry(key).or_insert(0);
}
println!("entry + or_insert: {:?}", start.elapsed());
// entry is typically faster due to:
// 1. Single hash lookup instead of two
// 2. Atomic operation without release/acquire cycle
// 3. No lock for computation with or_insert_with
}entry avoids double lookup and provides better performance for conditional insertion.
use dashmap::DashMap;
use std::sync::Arc;
use std::thread;
fn thread_safe_initialization() {
let cache: Arc<DashMap<String, String>> = Arc::new(DashMap::new());
let handles: Vec<_> = (0..5).map(|_| {
let cache = Arc::clone(&cache);
thread::spawn(move || {
// Only compute once, even with concurrent access
cache.entry("expensive_key".to_string())
.or_insert_with(|| {
// Simulate expensive computation
println!("Computing... (should only appear once)");
thread::sleep(std::time::Duration::from_millis(100));
"computed_value".to_string()
});
})
}).collect();
for handle in handles {
handle.join().unwrap();
}
// Value computed exactly once
assert_eq!(cache.get("expensive_key").unwrap().value(), "computed_value");
}or_insert_with ensures expensive computation happens only once.
use dashmap::DashMap;
use dashmap::mapref::entry::Entry;
fn detailed_entry_handling() {
let map: DashMap<String, i32> = DashMap::new();
match map.entry("key".to_string()) {
Entry::Occupied(entry) => {
// Key already exists
println!("Existing value: {}", entry.get());
// entry provides:
// - entry.get(): &V
// - entry.get_mut(): &mut V
// - entry.insert(v): V (returns old value)
// - entry.remove(): V (removes and returns value)
}
Entry::Vacant(entry) => {
// Key doesn't exist
println!("Key not found, inserting");
// entry provides:
// - entry.insert(v): &mut V
// - entry.or_insert(v): &mut V
}
}
// Example: update if exists, return different message if absent
let result = match map.entry("counter".to_string()) {
Entry::Occupied(mut entry) => {
*entry.get_mut() += 1;
format!("Incremented to {}", entry.get())
}
Entry::Vacant(entry) => {
entry.insert(0);
"Initialized to 0".to_string()
}
};
}The Entry enum provides type-safe handling for both cases.
use dashmap::DashMap;
use std::sync::Arc;
struct RequestCounter {
counts: DashMap<String, u64>,
}
impl RequestCounter {
fn new() -> Self {
Self {
counts: DashMap::new(),
}
}
// Using entry for atomic increment
fn increment(&self, endpoint: &str) -> u64 {
self.counts
.entry(endpoint.to_string())
.and_modify(|count| *count += 1)
.or_insert(1)
.value()
.clone()
}
// Alternative with get_mut (problematic)
fn increment_get_mut(&self, endpoint: &str) -> u64 {
if let Some(mut entry) = self.counts.get_mut(endpoint) {
*entry.value_mut() += 1;
*entry.value()
} else {
self.counts.insert(endpoint.to_string(), 1);
1
}
}
fn get_count(&self, endpoint: &str) -> u64 {
self.counts.get(endpoint)
.map(|e| *e.value())
.unwrap_or(0)
}
}Atomic operations with entry ensure accurate counting under concurrency.
use dashmap::DashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
struct ComputationCache {
cache: DashMap<String, (String, Instant)>,
ttl: Duration,
}
impl ComputationCache {
fn new(ttl: Duration) -> Self {
Self {
cache: DashMap::new(),
ttl,
}
}
fn get_or_compute<F>(&self, key: &str, compute: F) -> String
where
F: FnOnce() -> String,
{
use dashmap::mapref::entry::Entry;
match self.cache.entry(key.to_string()) {
Entry::Occupied(entry) => {
let (value, timestamp) = entry.get();
if timestamp.elapsed() < self.ttl {
return value.clone();
}
// TTL expired, recompute
let new_value = compute();
entry.insert((new_value.clone(), Instant::now()));
new_value
}
Entry::Vacant(entry) => {
let value = compute();
entry.insert((value.clone(), Instant::now()));
value
}
}
}
// Alternative using get_mut (less clean)
fn get_or_compute_get_mut<F>(&self, key: &str, compute: F) -> String
where
F: FnOnce() -> String,
{
if let Some(mut entry) = self.cache.get_mut(key) {
let (value, timestamp) = entry.value_mut();
if timestamp.elapsed() < self.ttl {
return value.clone();
}
// TTL expired - need to recompute
// Problem: lock held during computation
let new_value = compute();
*entry.value_mut() = (new_value.clone(), Instant::now());
new_value
} else {
let value = compute();
self.cache.insert(key.to_string(), (value.clone(), Instant::now()));
value
}
}
}entry provides cleaner TTL cache logic without holding locks during computation.
Method comparison:
| Aspect | get_mut | entry |
|--------|-----------|---------|
| Lock acquisition | Read, then write | Write on insert |
| Conditional insert | Two operations | Single atomic |
| Computation timing | Under lock | Before lock |
| Return type | Option<RefMut> | Entry enum |
| Use case | Known key exists | Conditional insertion |
Entry API benefits:
| Method | Behavior | Use Case |
|--------|----------|----------|
| or_insert(v) | Insert if absent | Simple defaults |
| or_insert_with(f) | Lazy computation | Expensive defaults |
| or_default() | Insert Default::default() | Default trait types |
| and_modify(f) | Modify if exists | Conditional update |
| and_modify().or_insert() | Modify or insert | Upsert pattern |
Key insight: DashMap::entry and get_mut serve different patterns. get_mut is for simple mutation when you know a key exists—it returns Option<RefMut>, requiring a separate insert call if the key is absent. entry provides an Entry enum that represents the two states (occupied/vacant) atomically, enabling conditional insertion without race conditions. The critical advantage is or_insert_with: the computation closure runs before the write lock is acquired, preventing lock contention during expensive operations. For concurrent code, entry with or_insert_with or and_modify is almost always the correct choice—it guarantees atomicity and avoids the subtle bugs from check-then-insert patterns. Reserve get_mut for cases where the key's existence is guaranteed and you only need in-place mutation.