HashMap能否用单一泛型约束键值?Rust Cacher改造技术问询
Hey there! Let's tackle your two Rust questions clearly and practically.
Short answer: Yes, but it's rarely useful in most scenarios.
A HashMap's key requires implementing the Hash and Eq traits, but its value has no mandatory trait bounds. Using a single generic means your key and value must be the same type, which severely limits flexibility. Here's a quick example of how that would look:
use std::collections::HashMap; // A HashMap where keys and values are the same type struct SameTypeMap<K>(HashMap<K, K>) where K: std::hash::Hash + Eq; fn main() { let mut map = SameTypeMap(HashMap::new()); map.0.insert("rust", "rust"); // Works: key and value are both &str // map.0.insert("rust", 42); // Fails: mismatched types (str vs i32) }
Unless you specifically need keys and values to be identical (a niche use case), it's far better to use two separate generics (K for keys, V for values) — this is the standard approach in Rust's built-in HashMap<K, V> for a reason!
The original Cacher from The Rust Programming Language only stores one value, but we can expand it to cache results for multiple inputs using a HashMap. Let's build this step by step.
First, here's the updated generic Cacher implementation:
use std::collections::HashMap; use std::hash::Hash; // Generic Cacher that stores multiple key-value pairs struct Cacher<F, K, V> where F: Fn(K) -> V, K: Eq + Hash + Clone, // K needs Hash/Eq for HashMap keys, Clone to reuse inputs V: Clone, // Clone to return copies of cached values (avoids lifetime complexity) { calculation: F, cached_values: HashMap<K, V>, } impl<F, K, V> Cacher<F, K, V> where F: Fn(K) -> V, K: Eq + Hash + Clone, V: Clone, { // Create a new Cacher with an empty cache fn new(calculation: F) -> Self { Cacher { calculation, cached_values: HashMap::new(), } } // Get a value: return cached if exists, calculate and store if not fn value(&mut self, arg: K) -> V { // Check cache first if let Some(result) = self.cached_values.get(&arg) { return result.clone(); } // Calculate, store, and return the result let result = (self.calculation)(arg.clone()); self.cached_values.insert(arg, result.clone()); result } }
How this works:
- Generics breakdown:
Fis the type of the function we're caching,Kis the function's input type (must be hashable and equatable for HashMap keys), andVis the function's return type. - Clone traits: We add
ClonetoKso we can store the input in the HashMap after using it for calculation.CloneonVlets us return a copy of the cached value instead of dealing with tricky reference lifetimes (keeping it aligned with the original Cacher's behavior). - Multi-key support: This Cacher automatically handles any number of unique inputs — just call
value()with different arguments, and it'll cache each result separately.
Testing it out:
fn main() { let mut cacher = Cacher::new(|num: u32| { println!("Calculating result for {}...", num); num * 3 }); // First call: calculates and caches println!("Result: {}", cacher.value(5)); // Second call: uses cached value (no calculation) println!("Result: {}", cacher.value(5)); // New input: calculates and caches again println!("Result: {}", cacher.value(10)); }
Bonus: Supporting multiple arguments
If you want to cache functions with multiple inputs, just wrap the arguments in a tuple (tuples automatically implement Hash and Eq if all their elements do):
let mut multi_arg_cacher = Cacher::new(|(a, b): (u32, u32)| { println!("Calculating sum of {} and {}...", a, b); a + b }); println!("Sum: {}", multi_arg_cacher.value((2, 3))); // Calculates once println!("Sum: {}", multi_arg_cacher.value((2, 3))); // Uses cache
内容的提问来源于stack exchange,提问作者Mike S.

