rust - 保持全局 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");
}
}
那么我们可以做些什么来使我们在结构中使用的值与全局映射中的值保持同步呢?
几个想法:
- 似乎我们要么必须在改变任何
值之前知道
MY_ITEMS
,我们需要知道我们想要改变的所有项目。 - multi_mut 确实具有迭代任意数量的 HashMap 值的函数
iter_multi_mut(keys, buffer)
,但这似乎不是一个非常灵活的设计。 - 我们也有可能
match MY_ITEMS.lock().unwrap().get_pair_mut()
返回某种FnMut(Mutex<HashMap<String, Entitity>>)
会对我们产生副作用的东西。 - 我们还可以通过带有更新信息的通道发送事件。
作为旁注,我认为将所有People
结构和我们所有的Roster
结构保存在同一个全局可访问变量中很重要。但也许除了 a 之外还有其他一些容器结构HashMap
可以更好地适应项目之间的依赖关系。
另一个旁注,因为 rust 的 HashMap 没有方便的方法来改变 HashMap 中的多个值,所以我使用multi_mut crate 的get_pair_mut()
方法来获取对MY_ITEMS
.
也许有一种不同的模式或设计更适合这个?
- 编辑:这是使用 Arc<Mutex> 的游乐场示例,但我认为包装器不是可扩展的解决方案。我认为一个更好的解决方案将涉及分割
Entities
我们想要变异的所有内容,然后对其进行迭代。
我也知道这个问题是一个很常见的生锈问题。也许这篇和这篇关于 Rust 中共享可变性的文章可以指向更好的设计。
解决方案
推荐阅读
- asp.net - WCF 服务迁移到 IIS 10 - 一致 503
- r - 提升分类树 gbm 字符变量
- azure - 创建 Azure Policy 和 Blueprint 所需的最低 IAM 角色是什么
- javascript - 单击循环中的 Var 图像
- makefile - How to make backtick commands fail in Makefiles?
- ruby - 如何分配变量而不引用它
- azure - 更新 web.config 以在 Azure 应用服务的 Azure DevOps 发布管道中添加特定环境的重写规则
- angular - 如何在 Angular 模板中禁用斜杠编码
- python - 在熊猫数据框中创建新列作为分组依据
- r - 当我没有分隔符时,如何将 R 中的列拆分为两列?