首页 > 解决方案 > 保持全局 hashmap 值与 struct hashmap 值同步

问题描述

我正在使用全局可访问的 HashMap 来存储同构实体Roster、 和Person.

现在我的Roster结构也有一个实体的 HashMap Roster.names,它是全局 HashMap 中实体的子集MY_ITEMS,所以Roster.names⊂MY_ITEMS

自然,我想获得可变引用,MY_ITEMS以便它们可以“交互”并相互变异。问题在于,在通过函数改变某些特定实体的过程中,可能需要更新其他实体以及副作用。

但在match MY_ITEMS.lock().unwrap().get_pair_mut("roster 1", "roster 2"){...}我们不能再从MY_ITEMS. 我们只能对我们通过请求的两个实体进行可变访问get_pair_mut()。因此,任何在此范围内更新第三个值的尝试MY_ITEMS都将导致WouldBlock.

这是问题的概括:

SET has A,B, and C
B.C = SET.C.clone()
get &mut A and &mut B from SET{
        A.change(B){
            updates(B.C)
            updates(B)
        }
}
SET.B.C !== SET.C

这是一些显示此设计问题的简单代码:

multi_mut = "0.1.3"
lazy_static = "1.4.0"
use lazy_static::lazy_static;
use std::sync::Mutex;
use std::collections::HashMap;
use multi_mut::{HashMapMultiMut};

//globally accessible Hashmap
lazy_static! {
    static ref MY_ITEMS: Mutex<HashMap<String, Entity>> = Mutex::new(HashMap::new());
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Person{
    pub roster_id: Option<String>,
    name: String,
    id: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Roster{
  pub id: String,
  pub names: HashMap<String, Entity>
}
//wrapper enum
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Entity{
    Roster(Roster),
    Person(Person),
}

impl Roster{
    pub fn new(id: String, start_names: Vec<(String, Person)>)->Self{
        let mut map = HashMap::new();
        map.extend(start_names.clone().into_iter().map(|n|{
            let mut altered = n.1.clone();
            altered.roster_id = Some(id.clone());
            //on adding a Person to a Roster, we also add them to the global map
            MY_ITEMS.lock().unwrap().insert(n.0.clone(), Entity::Person(altered.clone()));
            return (n.0, Entity::Person(altered))
        }));
        Roster{
            id: id, 
            names: map
        }
    }
    pub fn take_person(&mut self, from_roster: &mut Roster, to_take_id: String){
        //take a person from the other roster
        let person_entry = from_roster.names.remove_entry(&to_take_id).unwrap();
        //alter their information to reflect a change in which roster the person belongs too
        if let Entity::Person(mut p) = person_entry.1.clone(){
            p.roster_id = Some(self.id.clone());
            //update the roster with the changed persons value
            self.names.insert(person_entry.0.clone(), Entity::Person(p.clone()));
            //trying to insert the updated value into the global hashmap will cause a lock to fail
            //so value of person inside of this roster and inside of the global map becomes out of sync
            // MY_ITEMS.try_lock().unwrap().insert(person_entry.0.clone(), Entity::Person(p.clone()));
        }
    }
}

fn main(){
    //starting people for roster 1
    let group_1 = vec![
    (String::from("Person 1"), Person{roster_id: None, name: String::from("Josie"), id: String::from("Person 1")}),
    (String::from("Person 2"), Person{roster_id: None, name: String::from("Joon"), id: String::from("Person 2")}),
    (String::from("Person 3"), Person{roster_id: None, name: String::from("Glooby"), id: String::from("Person 3")})];
    
    let roster_1 = Roster::new(String::from("roster 1"), group_1);

    //starting people for roster 2
    let group_2 = vec![
        (String::from("Person 4"), Person{roster_id: None, name: String::from("Borg"), id: String::from("Person 4")}),
        (String::from("Person 5"), Person{roster_id: None, name: String::from("Snog"), id: String::from("Person 5")}),
        (String::from("Person 6"), Person{roster_id: None, name: String::from("Cloob"), id: String::from("Person 6")})];
    
    let roster_2 = Roster::new(String::from("roster 2"), group_2);

    //add rosters to the global map
    MY_ITEMS.lock().unwrap().insert(roster_1.id.clone(), Entity::Roster(roster_1.clone()));
    MY_ITEMS.lock().unwrap().insert(roster_2.id.clone(), Entity::Roster(roster_2.clone()));
    
    //remove Person 4, Borg, from roster 2 and insert them into roster 1
    match MY_ITEMS.lock().unwrap().get_pair_mut("roster 1", "roster 2"){
        //becuase of get_pair_mut, we can no longer lock MY_ITEMS in this block scope
        Some((r1_wrapper, r2_wrapper))=>{
            match (r1_wrapper, r2_wrapper){
                (Entity::Roster(r1), Entity::Roster(r2))=>{
                    r1.take_person(r2, String::from("Person 4"));
                }
                _=>()
            }
        }
        _=>()
    };


    if let Entity::Roster(r1) = MY_ITEMS.lock().unwrap().get("roster 1").unwrap(){
        //the roster_id field for Person 4 is updated in our roster_2 local variable
        dbg!(r1.names.get("Person 4"));//->Some(Person)
        //but it is not updated in our global map
        dbg!(roster_1.clone().names.get("Person 4"));//->None
        assert_eq!(r1.names.get("Person 4"), roster_1.names.get("Person 4"), "Roster value in local does not equal roster value in global");
    }
}

那么我们可以做些什么来使我们在结构中使用的值与全局映射中的值保持同步呢?

几个想法:

作为旁注,我认为将所有People结构和我们所有的Roster结构保存在同一个全局可访问变量中很重要。但也许除了 a 之外还有其他一些容器结构HashMap可以更好地适应项目之间的依赖关系。

另一个旁注,因为 rust 的 HashMap 没有方便的方法来改变 HashMap 中的多个值,所以我使用multi_mut crate 的get_pair_mut()方法来获取对MY_ITEMS.

也许有一种不同的模式或设计更适合这个?

我也知道这个问题是一个很常见的生锈问题。也许这篇这篇关于 Rust 中共享可变性的文章可以指向更好的设计。


标签: rust

解决方案


推荐阅读