HashMap borrow and use key on insert

Hey there, so basically there's a config file that contains information like different user profiles, and this config can be reloaded during operation so these profiles can changed or be deleted. The thing is, I don't necessarily want to delete profiles until I know nothing is going to attempt to re-read them, and I also want to be able to access them centrally, so just storing them in an Rc<RefCell<T>> and relying on the individual widgets to keep it alive doesn't fully work because the central mainloop also needs to be able to read them (I could use a Weak<T> here and kind of get what I want, but it's messy).

Now the thing reading the information is going to be a UI element implemented with gkt4rs, which heavily restricts how I handle borrowing, and basically requires that I overly rely on Rc<RefCell<T>> or Rc<T> for shared data. I could conceivably implement this with a hashmap containing Rc<RefCell<T>> and make each widget using the data hold a strong reference, but that wouldn't get rid of the need for the d field and that would mean I have to mutate shared data, which I'd prefer to avoid if possible. Also, I see this as an opportunity to learn more about the borrowing system, which is still a weak spot of mine.

Anyway, as you can see in the code below, there is the main storage structure (field s), and two additional collections, c and d, which use &'a K as their key types. All keys present in both c and d are references to keys within s, and those fields will be cleared before the referenced field in s is removed.

use std::{
    cmp::Eq,
    collections::{HashMap, HashSet},
    hash::Hash,
};

#[derive(Debug)]
pub struct Store<'a, K: 'a, V> {
    // use a hashmap to store items
    s: HashMap<K, V>,
    // HashMap containing the counts of how many things are currently using a
    // an item (kind of like a reference count, except the users aren't 
    // directly holding references). 
    c: HashMap<&'a K, u32>,
    // HashSet specifying what information should be deleted once nothing 
    // else is using it
    d: HashSet<&'a K>,
}

#[derive(Debug)]
pub struct StoreBuilder<'a, K, V>(Store<'a, K, V>);

impl<'a, K, V> !Sync for Store<'a, K, V> {}

impl<'a, K, V> Default for Store<'a, K, V>
where
    K: Eq + Hash + 'a,
{
    fn default() -> Self {
        Self {
            s: HashMap::default(),
            c: HashMap::default(),
            d: HashSet::default(),
        }
    }
}

impl<'a, K, V> Store<'a, K, V>
where
    K: Eq + Hash + 'a,
{
    pub fn builder() -> StoreBuilder<'a, K, V> {
        StoreBuilder(Self::default())
    }

    pub fn insert(&mut self, key: K, item: V) {
        // self.c.insert(&key, 0);
        // ^ uncommenting this line causes a compiler error
        self.s.insert(key, item);
    }

    pub fn peek(&self, key: &K) -> Option<&V> {
        self.s.get(key)
    }
}

impl<'a, K: Eq + Hash + 'a, V> StoreBuilder<'a, K, V>
where
    K: Eq + Hash + 'a,
{
    pub fn with(mut self, key: K, item: V) -> Self {
        self.0.insert(key, item);
        self
    }

    pub fn build(self) -> Store<'a, K, V> {
        self.0
    }
}

The first issue I am running into is within the Store::insert() function. Obviously there is a problem that I can't insert a reference to the key into c after inserting it into s because the value was moved into s, and I can't insert a reference into c before I move it into s because that's after borrow.

Are there any safe ways to insert an item into c that uses key: &'a K (owned by s) with cloning?

Thanks

Store<'a, K, V> is an attempt to create a self-referential struct, which isn't directly possible with actual references (&). Perhaps think of it this way: you could never modify s while d or c held keys (as you might delete data they're pointing to), but there's nothing in the language to convey that (and it would probably be too restrictive anyway). [1]


  1. You could also never move s, and thus self, as heap versus stack aren't distinguished at the language level either. ↩ī¸Ž

2 Likes

Ah, I see. That's a bit sad. I was hoping to avoid cloning or using Rc<T> or Weak<T>, but it sounds like that's something I can't avoid here if I try to keep going down that road. I suppose I could just nest everything into a larger containing structure, and deal with that inside of a single map, and that would hopefully work.