I would like to wrap a HashMap
with a struct where duplicate values are stored only once. My use case is that I want to insert very large Rule
objects into a HashMap
which are associated to a list of names (strings), but I don't want to clone()
the object for each insertion. So I figured that I would need to keep the storage of the rules in a Vec<Rule>
, and then just update a HashMap<String, Vec<&'a Rule>>
to store the references whenever an insertion happens.
My problem is that I cannot get the lifetimes to work out correctly, and I'm starting to suspect I might need to use unsafe
Rust, but I'm pretty novice in that area.
I'll paste my MCVE here (Example in Rust Playground):
use std::collections::HashMap;
struct Rule {
/// Names of people who this rule applies to.
pub names: Vec<String>,
/// Imagine the content is so large as to justify not wanting to clone it.
pub content: String,
}
struct RuleCollection<'a> {
rules: Vec<Rule>,
by_name_hash: HashMap<String, Vec<&'a Rule>>,
}
impl<'a> RuleCollection<'a> {
pub fn new() -> Self {
Self {
rules: vec![],
by_name_hash: HashMap::new(),
}
}
/// Insert a rule and update the by_name_hash.
pub fn insert(&mut self, names: Vec<String>, content: String) {
let new_rule = Rule {
names: names,
content: content,
};
// Store the raw rule into the vector and get a ref to it.
self.rules.push(new_rule);
let new_rule_ref = self.rules.last().unwrap();
// Update the `by_name_hash`.
for name in &new_rule_ref.names {
match self.by_name_hash.get_mut(name) {
Some(v) => v.push(new_rule_ref),
None => {
self.by_name_hash.insert(name.clone(), vec![new_rule_ref]);
}
}
}
}
/// Get a list of rules, given a name. This is what I want to optimize.
pub fn get(&mut self, name: &String) -> Option<&Vec<&'a Rule>> {
self.by_name_hash.get(name)
}
}
fn main() {
let mut collection = RuleCollection::new();
collection.insert(
vec![
"Alice".to_string(),
"Bob".to_string(),
"Charlie".to_string(),
],
"content1".to_string(),
);
collection.insert(
vec!["Dave".to_string(), "Bob".to_string()],
"content2".to_string(),
);
// Alice should have 1 rule and Bob should have 2.
assert_eq!(collection.get(&"Alice".to_string()).unwrap().len(), 1);
assert_eq!(collection.get(&"Bob".to_string()).unwrap().len(), 2);
}
And here is the compiler error:
Compiling sandbox v0.1.0 (/Users/gns/sandbox/rust/sandbox)
error: lifetime may not live long enough
--> src/main.rs:79:28
|
57 | impl<'a> RuleCollection<'a> {
| -- lifetime `'a` defined here
...
66 | pub fn insert(&mut self, names: Vec<String>, content: String) {
| - let's call the lifetime of this reference `'1`
...
79 | Some(v) => v.push(new_rule_ref),
| ^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
error: could not compile `sandbox` due to previous error
What is the easiest and most idiomatic way to accomplish this task?