Доступ к двум изменяемым ссылкам из глобальной хэш-карты одновременно в Rust

Скажем, у нас есть глобально доступная хеш-карта трейт-объектов, которую мы создаем с помощью lazy_static: MY_ANIMALS: Mutex<HashMap<i32, AnimalBox>>, где type AnimalBox = Box<dyn AnimalExt+Send>

Теперь мы хотим, чтобы животные в этой глобальной хеш-карте взаимодействовали друг с другом. Например, один AnimalBox может AnimalExt::eat(&mut self, prey: &mut AnimalBox) другого.

Проблема в том, что наша функция eat() требует как изменяемой ссылки на себя, так и изменяемой ссылки на молитву (потому что мы хотим, чтобы молитва AnimalExt::perish(&mut self) была съедена.

Однако получение двух изменяемых ссылок на нашу хеш-карту вызывает ошибку WouldBlock:

use lazy_static::lazy_static;
use std::sync::Mutex;
use std::collections::HashMap;

//type alias for our boxed animals
type AnimalBox = Box<dyn AnimalExt+Send>;

//globally accessible hashmap for keeping track of our animals throughout the scope of our application
lazy_static! {
    static ref MY_ANIMALS: Mutex<HashMap<i32, AnimalBox>> = Mutex::new(HashMap::new());
}

//simple trait for our animals
trait AnimalExt{
    //eat() function requires a mutable reference to another AnimalBox
    fn eat(&mut self, pray: &mut AnimalBox);
    fn perish(&mut self);
    fn energy(&self)->i32;
    fn id(&self)->i32;
}

struct Wolf{
    id: i32,
    energy: i32,
    alive: bool,
}
impl AnimalExt for Wolf{
    fn id(&self)->i32{
        self.id
    }
    fn eat(&mut self, pray: &mut AnimalBox) {
        pray.perish();
        self.energy+= pray.energy()
    }
    fn energy(&self) ->i32 {
        self.energy
    }
    fn perish(&mut self){
        self.alive = false; 
    }
}
impl Wolf{
    pub fn new(id: i32)->Self{
        Wolf{
            id: id,
            energy: 50,
            alive: true,
        }
    }
}
struct Cow{
    id: i32,
    energy: i32,
    alive: bool,
}
impl Cow{
    pub fn new(id: i32)->Self{
        Cow{
            id: id,
            energy: 100,
            alive: true,
        }
    }
}
impl AnimalExt for Cow{
    fn id(&self)->i32{
        self.id
    }
    fn eat(&mut self, pray: &mut AnimalBox) {
        pray.perish();
        self.energy+= pray.energy()
    }
    fn energy(&self) ->i32 {
        self.energy
    }
    fn perish(&mut self){
        self.alive = false; 
    }
}
fn main() {
    println!("Hello, world!");
    //define our animals
    let cow1 = Box::new(Cow::new(1)) as AnimalBox;
    let cow2 = Box::new(Cow::new(2)) as AnimalBox;
    let wolf1 = Box::new(Wolf::new(3)) as AnimalBox;
    let wolf2 = Box::new(Wolf::new(4)) as AnimalBox;

    //insert them into the global hashmap
    MY_ANIMALS.lock().unwrap().insert(cow1.id(), cow1);
    MY_ANIMALS.lock().unwrap().insert(cow2.id(), cow2);
    MY_ANIMALS.lock().unwrap().insert(wolf1.id(), wolf1);
    MY_ANIMALS.lock().unwrap().insert(wolf2.id(), wolf2);

    //getting one animal to eat() another causes a WouldBlock error
    match (MY_ANIMALS.try_lock().unwrap().get_mut(&0), MY_ANIMALS.try_lock().unwrap().get_mut(&1)){
        (Some(a1), Some(a2))=>{
            a1.eat(a2);
        }
        _=>()
    }
}

Есть ли хорошая работа для этого? Или нет безопасного способа сделать это с помощью хэш-карты? Я видел этот ответ на аналогичный вопрос, но предлагаемый ответ предлагает использовать RefCell, что несовместимо с требованием черты lazy_static Send.


person ANimator120    schedule 03.07.2021    source источник
comment
Отвечает ли это на ваш вопрос? Заимствовать два изменяемых значения из одного и того же HashMap   -  person kmdreko    schedule 03.07.2021
comment
@kmdreko Я связался с этим ответом в своем вопросе. Проблема заключается в требовании ```RefCell``, и я бы предпочел избегать небезопасного кода (хотя я понимаю, если это не совсем практическое желание). Можем ли мы просто заменить RefCell, который они используют, на Mutex?   -  person ANimator120    schedule 03.07.2021
comment
После подробного прочтения ответа я вижу, что они предлагают использовать Mutex в тех случаях, когда требуется безопасность потоков, поэтому я думаю, что этот вопрос является дубликатом.   -  person ANimator120    schedule 03.07.2021


Ответы (1)


В итоге я использовал ящик muti_mut, который предоставляет несколько методов для нескольких изменяемых ссылок на HashMap или BTreeMap. .

use lazy_static::lazy_static;
use std::sync::Mutex;
use std::collections::HashMap;
use multi_mut::{HashMapMultiMut, HashMapMutWrapper};

//type alias for our boxed animals
type AnimalBox = Box<dyn AnimalExt+Send>;

//globally accessible Hashmap for keeping track of our animals throughout the scope of our application
lazy_static! {
    static ref MY_ANIMALS: Mutex<HashMap<i32, AnimalBox>> = Mutex::new(HashMap::new());
}

//simple trait
trait AnimalExt{
    //eat() function requires a mutable reference to another AnimalBox
    fn eat(&mut self, pray: &mut AnimalBox);
    fn perish(&mut self);
    fn energy(&self)->i32;
    fn id(&self)->i32;
}

struct Wolf{
    id: i32,
    energy: i32,
    alive: bool,
}
impl AnimalExt for Wolf{
    fn id(&self)->i32{
        self.id
    }
    fn eat(&mut self, pray: &mut AnimalBox) {
        pray.perish();
        self.energy+= pray.energy()
    }
    fn energy(&self) ->i32 {
        self.energy
    }
    fn perish(&mut self){
        self.alive = false; 
    }
}
impl Wolf{
    pub fn new(id: i32)->Self{
        Wolf{
            id: id,
            energy: 50,
            alive: true,
        }
    }
}
struct Cow{
    id: i32,
    energy: i32,
    alive: bool,
}
impl Cow{
    pub fn new(id: i32)->Self{
        Cow{
            id: id,
            energy: 100,
            alive: true,
        }
    }
}
impl AnimalExt for Cow{
    fn id(&self)->i32{
        self.id
    }
    fn eat(&mut self, pray: &mut AnimalBox) {
        pray.perish();
        self.energy+= pray.energy()
    }
    fn energy(&self) ->i32 {
        self.energy
    }
    fn perish(&mut self){
        self.alive = false; 
    }
}
fn main() {
    println!("Hello, world!");
    //define our animals
    let cow1 = Box::new(Cow::new(1)) as AnimalBox;
    let wolf1 = Box::new(Wolf::new(2)) as AnimalBox;
    let before_eating_cow = wolf1.energy();
    //insert them into the global hashmap
    MY_ANIMALS.lock().unwrap().insert(cow1.id(), cow1);
    MY_ANIMALS.lock().unwrap().insert(wolf1.id(), wolf1);


    //use get_pair_mut method from the multi_mut crate
    match MY_ANIMALS.try_lock().unwrap().get_pair_mut(&1, &2){
        Some((hunter, prey))=>{
            dbg!("hunter eats prey");
            hunter.eat(prey);
        }
        None=>()
    }
    let after_eating_cow = MY_ANIMALS.lock().unwrap().get(&1).unwrap().energy();
    assert_ne!(before_eating_cow, after_eating_cow);
}
person ANimator120    schedule 04.07.2021