How does lru::LruCache::put handle existing entries compared to promote for cache policy enforcement?

put performs a full insert-or-update operation that places the entry at the most-recently-used position and may evict the least-recently-used entry if the cache is at capacity, while promote only moves an existing entry to the most-recently-used position without modifying its value or triggering eviction—the entry must already exist, and promote is purely an LRU order manipulation. The key distinction is that put is a write operation that can change cache contents and size, whereas promote is a read-positioning operation that only affects LRU ordering for an existing entry. Both operations update the cache's LRU state, but only put has side effects on the cache's contents and potential evictions.

Basic put and promote Operations

use lru::LruCache;
 
fn basic_operations() {
    let mut cache: LruCache<String, i32> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    // put inserts new entries at most-recently-used position
    cache.put("a".to_string(), 1);
    cache.put("b".to_string(), 2);
    cache.put("c".to_string(), 3);
    // Order (MRU to LRU): c, b, a
    
    // promote moves existing entry to MRU position
    cache.promote(&"a".to_string());
    // Order (MRU to LRU): a, c, b
    
    // put with existing key updates value AND moves to MRU
    cache.put("b".to_string(), 20);
    // Order (MRU to LRU): b, a, c
    // "b" now has value 20
}

Both operations affect LRU ordering, but put also affects cache contents.

put for New Entries

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn put_new_entry() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(2).unwrap());
    
    // Insert first entry
    cache.put(1, "one".to_string());
    assert_eq!(cache.len(), 1);
    assert_eq!(cache.get(&1), Some(&"one".to_string()));
    
    // Insert second entry
    cache.put(2, "two".to_string());
    assert_eq!(cache.len(), 2);
    
    // Cache is now at capacity
}

put adds entries, potentially filling the cache to capacity.

put with Eviction

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn put_with_eviction() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(2).unwrap());
    
    cache.put(1, "one".to_string());
    cache.put(2, "two".to_string());
    // Cache is full
    
    // Adding third entry evicts LRU (key 1)
    let evicted = cache.put(3, "three".to_string());
    
    // put returns the evicted entry (if any)
    assert_eq!(evicted, Some((1, "one".to_string())));
    assert_eq!(cache.get(&1), None);  // Evicted
    assert_eq!(cache.get(&2), Some(&"two".to_string()));
    assert_eq!(cache.get(&3), Some(&"three".to_string()));
}

put returns the evicted entry when it triggers eviction.

promote Only Affects Order

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn promote_order_only() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, "one".to_string());
    cache.put(2, "two".to_string());
    cache.put(3, "three".to_string());
    // Order: 3 (MRU), 2, 1 (LRU)
    
    // promote moves 1 to MRU position
    cache.promote(&1);
    // Order: 1 (MRU), 3, 2 (LRU)
    
    // Value is unchanged
    assert_eq!(cache.get(&1), Some(&"one".to_string()));
    
    // promote does NOT return anything
    // It just reorders
}

promote reorders without changing values or triggering eviction.

promote Requires Existing Entry

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn promote_existing_only() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, "one".to_string());
    
    // promote on existing entry: works
    cache.promote(&1);  // No-op, already MRU
    
    // promote on non-existing entry: does nothing
    cache.promote(&99);  // No error, no effect
    
    // No eviction, no insertion
    assert_eq!(cache.len(), 1);
    assert_eq!(cache.get(&99), None);
}

promote silently ignores non-existent keys—it cannot insert or evict.

put Updates Existing Values

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn put_updates_value() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, "one".to_string());
    
    // put with same key updates value
    let evicted = cache.put(1, "ONE".to_string());
    
    // No eviction (key already exists, just updated)
    assert_eq!(evicted, None);
    
    // Value is updated
    assert_eq!(cache.get(&1), Some(&"ONE".to_string()));
    
    // Key is now MRU
}

Updating an existing entry with put changes the value and moves to MRU.

Comparison Table

// put:
// - Inserts new entries
// - Updates existing entries
// - Moves entry to MRU position
// - May evict LRU entry if at capacity
// - Returns evicted entry (if any)
// - Can change cache size (for new entries)
// - Always affects cache state
 
// promote:
// - Only works on existing entries
// - Does NOT modify values
// - Moves entry to MRU position
// - Does NOT trigger eviction
// - Returns nothing
// - Does NOT change cache size
// - Only affects LRU ordering

The two operations serve different purposes in LRU management.

get vs promote

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn get_vs_promote() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, "one".to_string());
    cache.put(2, "two".to_string());
    cache.put(3, "three".to_string());
    // Order: 3, 2, 1
    
    // get AND promote (moves to MRU)
    let value = cache.get(&1);
    // Order: 1, 3, 2 (1 moved to MRU)
    
    // peek AND NOT promote (keeps position)
    let value = cache.peek(&2);
    // Order: still 1, 3, 2
    
    // promote explicitly (moves to MRU)
    cache.promote(&2);
    // Order: 2, 1, 3
}

get is a read that promotes; promote is just promotion without reading.

When to Use Each

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn use_cases() {
    let mut cache: LruCache<String, Data> = LruCache::new(NonZeroUsize::new(100).unwrap());
    
    // Use put when:
    
    // 1. Adding new entries
    cache.put("key1".to_string(), Data::new());
    
    // 2. Updating existing entries
    cache.put("key1".to_string(), Data::updated());
    
    // 3. Want eviction behavior
    let evicted = cache.put("new_key".to_string(), Data::new());
    if let Some((key, value)) = evicted {
        // Handle evicted entry
    }
    
    // Use promote when:
    
    // 1. Refreshing entry position without reading
    cache.promote(&"key1".to_string());
    
    // 2. Manual LRU management after peek
    if let Some(data) = cache.peek(&"key1".to_string()) {
        // Inspect without promoting
        if data.is_important() {
            // Now promote
            cache.promote(&"key1".to_string());
        }
    }
    
    // 3. Bulk reorder after loading cache
    for key in &priority_keys {
        cache.promote(key);
    }
}

Use put for write operations, promote for manual LRU manipulation.

Eviction Behavior Differences

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn eviction_differences() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(2).unwrap());
    
    cache.put(1, "one".to_string());
    cache.put(2, "two".to_string());
    
    // put triggers eviction when full
    let evicted = cache.put(3, "three".to_string());
    assert!(evicted.is_some());  // Key 1 was evicted
    
    // promote does NOT trigger eviction
    cache.put(4, "four".to_string());  // First evict key 2
    cache.put(5, "five".to_string());   // Then evict key 3
    
    // Cache now: 5, 4
    cache.promote(&4);  // Just reorders
    // Cache now: 4, 5
    // No eviction occurred
}

Only put can cause eviction; promote never does.

Manual LRU Policy Manipulation

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn manual_lru() {
    let mut cache: LruCache<String, i32> = LruCache::new(NonZeroUsize::new(5).unwrap());
    
    // Load initial entries
    for i in 0..5 {
        cache.put(format
!("key{}", i), i);
    }
    // Order (MRU to LRU): key4, key3, key2, key1, key0
    
    // Promote specific entries based on external priority
    // Without reading or modifying values
    cache.promote(&"key0".to_string());
    cache.promote(&"key1".to_string());
    // Order: key1, key0, key4, key3, key2
    
    // Now key2 and key3 are at LRU positions
    // Next put will evict key2
}

promote allows manual LRU manipulation for custom policies.

Return Value Differences

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn return_values() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(2).unwrap());
    
    // put returns Option<(K, V)> - the evicted entry
    let result1 = cache.put(1, "one".to_string());
    assert_eq!(result1, None);  // No eviction
    
    let result2 = cache.put(2, "two".to_string());
    assert_eq!(result2, None);  // No eviction
    
    let result3 = cache.put(3, "three".to_string());
    assert_eq!(result3, Some((1, "one".to_string())));  // Evicted!
    
    // promote returns () - nothing
    cache.promote(&2);  // Returns nothing, just reorders
}

put returns the evicted entry; promote returns nothing.

Working with References

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn reference_handling() {
    let mut cache: LruCache<String, i32> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put("key".to_string(), 1);
    
    // put takes ownership of key and value
    cache.put("key".to_string(), 2);
    
    // promote takes reference to key
    cache.promote(&"key".to_string());
    
    // This allows promote without consuming the key
    let key = String::from("key");
    cache.promote(&key);  // key still usable
    println!("Key: {}", key);  // key still valid
}

promote borrows the key; put takes ownership (for new keys).

contains_key and promote

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn contains_and_promote() {
    let mut cache: LruCache<i32, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, "one".to_string());
    
    // Check if key exists before promote
    if cache.contains_key(&1) {
        cache.promote(&1);
    }
    
    // contains_key does NOT promote
    // Order remains: 1 (MRU)
    
    // peek_mut allows modification without promotion
    if let Some(value) = cache.peek_mut(&1) {
        value.push_str("_modified");
    }
    // Entry still at same position in LRU
}

contains_key and peek don't promote; use promote explicitly when needed.

Pattern: Conditional Promotion

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn conditional_promotion() {
    let mut cache: LruCache<String, Data> = LruCache::new(NonZeroUsize::new(100).unwrap());
    
    // Load cache
    for i in 0..100 {
        cache.put(format
!("item{}", i), Data::new(i));
    }
    
    // Process items, conditionally promote
    let important_keys = vec
!["item0", "item50", "item99"];
    
    for key in &important_keys {
        // Only promote if exists and meets criteria
        if let Some(data) = cache.peek(key) {
            if data.is_important() {
                cache.promote(key);
            }
        }
    }
    
    // Important items now at MRU positions
    // Less likely to be evicted
}
 
struct Data {
    id: i32,
}
 
impl Data {
    fn new(id: i32) -> Self { Data { id } }
    fn is_important(&self) -> bool { self.id % 50 == 0 }
}

Combine peek and promote for conditional LRU updates.

Pattern: Refresh Without Reading

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn refresh_pattern() {
    let mut cache: LruCache<String, String> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put("a".to_string(), "data_a".to_string());
    cache.put("b".to_string(), "data_b".to_string());
    cache.put("c".to_string(), "data_c".to_string());
    // Order: c, b, a
    
    // External signal: "a" is still relevant, don't evict
    cache.promote(&"a".to_string());
    // Order: a, c, b
    
    // Next eviction will remove "b" instead of "a"
    cache.put("d".to_string(), "data_d".to_string());
    // "b" is evicted, not "a"
    assert!(cache.contains_key(&"a"));
    assert!(!cache.contains_key(&"b"));
}

Use promote to keep entries alive without modifying them.

Iteration Order

use lru::LruCache;
use std::num::NonZeroUsize;
 
fn iteration_order() {
    let mut cache: LruCache<i32, i32> = LruCache::new(NonZeroUsize::new(3).unwrap());
    
    cache.put(1, 1);
    cache.put(2, 2);
    cache.put(3, 3);
    // Order: 3, 2, 1
    
    // Iteration is from MRU to LRU
    let keys: Vec<_> = cache.iter().map(|(&k, _)| k).collect();
    assert_eq!(keys, vec
![3, 2, 1]);
    
    // After promote
    cache.promote(&1);
    // Order: 1, 3, 2
    
    let keys: Vec<_> = cache.iter().map(|(&k, _)| k).collect();
    assert_eq!(keys, vec
![1, 3, 2]);
}

Iteration order reflects LRU order; promote changes this order.

Synthesis

Key differences:

// put:
// - Can insert new entries
// - Can update existing entries
// - Can trigger eviction
// - Returns evicted entry
// - Takes ownership of key and value
// - Full write operation
 
// promote:
// - Cannot insert new entries
// - Cannot update values
// - Cannot trigger eviction
// - Returns nothing
// - Takes reference to key
// - Pure ordering operation

When to use each:

// Use put when:
// - Adding new data to cache
// - Updating cached values
// - Managing cache lifecycle (handle evictions)
// - Standard cache access pattern
 
// Use promote when:
// - Refreshing entry position without reading
// - Conditional promotion based on peek inspection
// - Batch reordering after bulk operations
// - Custom LRU policy implementation

Common patterns:

// Standard cache access:
// get() promotes automatically
// peek() doesn't promote
// put() inserts/updates and promotes
 
// Manual promotion:
// 1. peek() to inspect
// 2. Decide if important
// 3. promote() if needed
 
// Eviction handling:
// put() returns evicted entry
// promote() never causes eviction

Key insight: put is a complete cache write operation that can insert, update, and evict, while promote is a focused LRU order manipulation that only moves existing entries to the most-recently-used position. put affects cache contents, size, and ordering, returning any evicted entry; promote affects only ordering, silently ignoring non-existent keys. Use put for normal cache writes and updates, and promote for manual LRU management when you want to refresh an entry's position without reading it (which would promote anyway) or when implementing custom cache policies that need fine-grained control over which entries stay and which get evicted.