0

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?

Greg Schmit
  • 4,275
  • 2
  • 21
  • 36

0 Answers0