What is the difference between 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.

Basic DashMap 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.

get_mut for Conditional Mutation

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.

The Race Condition with get_mut

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.

entry API for Atomic Conditional Operations

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.

or_insert_with for Lazy Computation

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.

Comparison: Entry vs get_mut Patterns

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.

Lock Behavior Differences

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.

Concurrent Access Patterns

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.

Entry API Methods

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.

Complex Conditional Insertion

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.

get_mut Use Cases

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.

Performance Comparison

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.

Thread-Safe Initialization Pattern

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.

Entry with Occupied/Vacant Handling

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.

Real-World Example: Request Counter

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.

Real-World Example: Caching with Computation

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.

Synthesis

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.