What is the difference between lru::LruCache::promote and get for explicitly updating LRU ordering?
get retrieves a value while updating its position to most-recently-used, while promote updates the position without returning the value. Both methods affect the LRU ordering, but promote is designed for cases where you need to manipulate the ordering without incurring the cost of cloning or returning the value.
LRU Cache Basics
use lru::LruCache;
fn lru_basics() {
// LRU cache evicts least recently used items when full
let mut cache: LruCache<&str, i32> = LruCache::new(3);
// Insert items
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// Cache is now: [a:1, b:2, c:3] (oldest to newest)
// Accessing updates ordering
cache.get(&"a"); // "a" becomes most recent
// Order now: [b:2, c:3, a:1] (a moved to end)
}LRU caches maintain access order, moving items to "most recent" on access.
The get Method
use lru::LruCache;
fn get_behavior() {
let mut cache: LruCache<&str, i32> = LruCache::new(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// get returns Option<&V> and updates ordering
let value = cache.get(&"a"); // Returns Some(&1)
// Side effect: "a" is now most recently used
// Order changed: [b:2, c:3, a:1]
assert_eq!(value, Some(&1));
// get returns None if key doesn't exist
let missing = cache.get(&"d");
assert_eq!(missing, None);
}get returns the value reference and moves the entry to most-recently-used position.
The promote Method
use lru::LruCache;
fn promote_behavior() {
let mut cache: LruCache<&str, i32> = LruCache::new(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// promote updates ordering WITHOUT returning the value
cache.promote(&"a");
// Order changed: [b:2, c:3, a:1]
// No value returned
// promote returns bool: true if key existed
let existed = cache.promote(&"a"); // true
let missing = cache.promote(&"d"); // false
}promote updates ordering and returns bool indicating whether the key existed.
Key Differences
use lru::LruCache;
fn comparison() {
let mut cache: LruCache<&str, String> = LruCache::new(3);
cache.put("a", "expensive to clone".to_string());
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Aspect │ get │ promote │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ Return type │ Option<&V> │ bool │
// │ Ordering update │ Yes │ Yes │
// │ Returns value │ Yes │ No │
// │ Purpose │ Access + update │ Update only │
// │ When key missing │ Returns None │ Returns false │
// └─────────────────────────────────────────────────────────────────────────┘
// Both update ordering
cache.get(&"a"); // Returns Some(&"expensive to clone")
cache.promote(&"a"); // Returns true, no value returned
}Both methods update ordering; the difference is what they return.
When to Use promote
use lru::LruCache;
fn when_to_use_promote() {
let mut cache: LruCache<&str, String> = LruCache::new(3);
// Store expensive-to-clone values
cache.put("key", "very large string data".to_string());
// Scenario 1: You already have a reference to the value
// and just need to update ordering
if let Some(value) = cache.peek(&"key") {
// Do something with value
println!("Value: {}", value);
// Now update ordering without getting the value again
cache.promote(&"key");
}
// Scenario 2: External access tracking
// You know an item was accessed externally and want to refresh it
fn on_external_access(cache: &mut LruCache<&str, Data>, key: &str) {
// Just update ordering, don't need the value
cache.promote(key);
}
}Use promote when you need to update ordering but don't need the value returned.
The peek Method (No Ordering Update)
use lru::LruCache;
fn peek_vs_get_vs_promote() {
let mut cache: LruCache<&str, i32> = LruCache::new(3);
cache.put("a", 1);
cache.put("b", 2);
cache.put("c", 3);
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Method │ Returns │ Updates Ordering │ Use Case │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ get │ Option<&V> │ Yes │ Normal access │
// │ peek │ Option<&V> │ No │ Inspect without affecting │
// │ promote │ bool │ Yes │ Update ordering only │
// └─────────────────────────────────────────────────────────────────────────┘
// peek: get without updating ordering
let value = cache.peek(&"a"); // Returns Some(&1)
// Order unchanged: [a:1, b:2, c:3]
// get: get with ordering update
let value = cache.get(&"a"); // Returns Some(&1)
// Order changed: [b:2, c:3, a:1]
// promote: update ordering without returning value
let existed = cache.promote(&"b"); // Returns true
// Order changed: [c:3, a:1, b:2]
}Three methods for three different access patterns: get (access + update), peek (access only), promote (update only).
Combining peek and promote
use lru::LruCache;
fn peek_and_promote() {
let mut cache: LruCache<&str, String> = LruCache::new(3);
cache.put("key", "data".to_string());
// Common pattern: peek first, conditionally promote
if let Some(value) = cache.peek(&"key") {
if value.starts_with("important") {
// Only update ordering for important items
cache.promote(&"key");
}
}
// This is equivalent to a conditional get:
if let Some(value) = cache.peek(&"key") {
if value.starts_with("important") {
cache.get(&"key"); // Would update ordering
}
}
}Combining peek and promote allows fine-grained control over ordering updates.
Performance Considerations
use lru::LruCache;
fn performance() {
let mut cache: LruCache<u64, LargeStruct> = LruCache::new(1000);
// Assume LargeStruct is expensive to clone
// and we want to track access patterns
// Using get: returns reference (no clone), but still has lookup overhead
if let Some(value) = cache.get(&42) {
// Use value
}
// Using promote: no reference returned, minimal overhead
// Useful when you just need to mark something as recently used
cache.promote(&42);
}
struct LargeStruct {
data: [u8; 1024],
}promote avoids the overhead of returning a reference when you only need ordering updates.
Use Cases for promote
use lru::LruCache;
fn use_cases() {
let mut cache: LruCache<&str, Data> = LruCache::new(100);
// 1. Background refresh patterns
fn background_refresh(cache: &mut LruCache<&str, Data>, key: &str) {
// External system indicates data was accessed
// We want to keep it in cache but don't need the value
cache.promote(key);
}
// 2. Prefetching with ordering
fn prefetch_accessed(cache: &mut LruCache<&str, Data>, accessed_keys: &[&str]) {
// Update ordering for all accessed keys
for key in accessed_keys {
cache.promote(key);
}
}
// 3. Manual LRU manipulation
fn explicit_ordering(cache: &mut LruCache<&str, Data>) {
// Move specific items to front/back of LRU
cache.promote(&"important"); // Make it most recent
}
// 4. Avoiding borrow checker issues
fn complex_scenario(cache: &mut LruCache<&str, Data>) {
// When you have a reference to the value already
// and just need to update ordering
if let Some(value) = cache.peek(&"key") {
process_value(value);
// Can't call get() here (would need &mut cache)
// But we can call promote after processing
}
cache.promote(&"key");
}
}
struct Data;
fn process_value(_: &Data) {}promote is useful for background tasks, explicit ordering, and avoiding borrow conflicts.
Implementation Details
use lru::LruCache;
fn implementation_notes() {
// Both get and promote use the same underlying mechanism
// to update the LRU ordering (moving node to front of list)
// The difference is purely in return value:
// - get: returns Option<&V> (reference to value)
// - promote: returns bool (did the key exist)
// Time complexity is the same: O(1)
// Both operations are constant time
let mut cache: LruCache<i32, i32> = LruCache::new(100);
// These have the same performance characteristics
cache.get(&1); // O(1) lookup + ordering update
cache.promote(&1); // O(1) lookup + ordering update
// The difference is just what they return
}Both methods have identical performance characteristics; the difference is return value.
Working with Contains
use lru::LruCache;
fn contains_pattern() {
let mut cache: LruCache<&str, i32> = LruCache::new(3);
cache.put("a", 1);
// contains does NOT update ordering
let exists = cache.contains(&"a"); // true, but order unchanged
// If you want to check existence AND update ordering:
// Option 1: use get
if cache.get(&"a").is_some() {
// Exists and ordering updated
}
// Option 2: use contains + promote
if cache.contains(&"a") {
cache.promote(&"a"); // Update ordering separately
}
}contains checks existence without updating ordering; combine with promote for explicit control.
Iteration and Promotion
use lru::LruCache;
fn iteration_promotion() {
let mut cache: LruCache<i32, String> = LruCache::new(5);
for i in 0..5 {
cache.put(i, format!("value{}", i));
}
// Promote even numbers
for key in 0..5 {
if key % 2 == 0 {
cache.promote(&key);
}
}
// Even numbers are now most recently used
// Order: [1, 3, 0, 2, 4] (roughly)
}Use promote in loops when you need to update ordering without accessing values.
Complete Example
use lru::LruCache;
struct CacheManager {
cache: LruCache<String, Vec<u8>>,
}
impl CacheManager {
fn new(capacity: usize) -> Self {
Self {
cache: LruCache::new(capacity),
}
}
// Normal access: get value and update ordering
fn get(&mut self, key: &str) -> Option<&Vec<u8>> {
self.cache.get(&key.to_string())
}
// Inspect without affecting ordering
fn peek(&self, key: &str) -> Option<&Vec<u8>> {
self.cache.peek(&key.to_string())
}
// Mark as recently used (from external signal)
fn mark_accessed(&mut self, key: &str) -> bool {
self.cache.promote(&key.to_string())
}
// Batch promote multiple keys
fn promote_batch(&mut self, keys: &[&str]) {
for key in keys {
self.cache.promote(&key.to_string());
}
}
}
fn main() {
let mut manager = CacheManager::new(100);
// Insert data
manager.cache.put("file1".to_string(), vec
![1, 2, 3]);
// Normal access
if let Some(data) = manager.get("file1") {
println!("Data: {:?}", data);
}
// External access notification
manager.mark_accessed("file1");
// Batch promotion
manager.promote_batch(&["file1", "file2", "file3"]);
}Summary
use lru::LruCache;
fn summary() {
// ┌─────────────────────────────────────────────────────────────────────────┐
// │ Method │ Returns │ Updates Order │ When to Use │
// ├─────────────────────────────────────────────────────────────────────────┤
// │ get │ Option<&V> │ Yes │ Normal access, need value │
// │ peek │ Option<&V> │ No │ Inspect without affecting │
// │ promote │ bool │ Yes │ Update ordering only │
// │ contains │ bool │ No │ Check existence │
// └─────────────────────────────────────────────────────────────────────────┘
// Key distinctions:
// - get: returns value + updates ordering
// - promote: updates ordering, returns bool (existed)
// - peek: returns value, no ordering update
// - contains: returns bool, no ordering update
// Use promote when:
// 1. You need to update ordering but not access the value
// 2. External system signals access (background refresh)
// 3. Batch updating multiple entries' ordering
// 4. Avoiding duplicate lookups when value is already known
}Key insight: get and promote both update LRU ordering, but get returns the value while promote returns only whether the key existed. Use get for normal access patterns where you need the value, peek to inspect without affecting ordering, and promote when you need to explicitly update ordering without incurring the overhead or semantics of returning the value. The three methods provide complete control over the LRU cache's ordering behavior.
