Can't dereference static HashMap entry for mutation, is my structure sound at all?


#1

Hi,

I’m trying to build a little framework that has at its center an event dispatching system loop. Users add events, and can trigger them with a little macro that adds the event data to a Vec in a HashMap. Since every event has a different type of event data the HashMap is over Any. The code looks like this right now:

 lazy_static! {
	pub static ref EventQueues: SharedMutex<HashMap<String, Box<Any+'static+Sync>>> = SharedMutex::new(HashMap::new());
}

pub fn event_queue_push<T>(event_name: &String, event: T) where T: Any+'static+Sync {
	let mut map = EventQueues.write().unwrap();
	let mut entry = *map.entry(event_name.clone()).or_insert(Box::new(Vec::<T>::new()));
	let mut casted_entry = entry as Box<Any + 'static>;
	let mut vec = casted_entry.downcast_mut::<Vec<T>>().unwrap();
	vec.push(event);
}
src/events/mod.rs:25:18: 25:85 error: cannot move out of borrowed content [E0507]
src/events/mod.rs:25 	let mut entry = *map.entry(event_name.clone()).or_insert(Box::new(Vec::<T>::new()));

The borrow checker says I can’t dereference the entry, as it’s borrowed and I guess the Box doesn’t implement Copy. Does that mean I should use something else than Box? Am I going in the right direction at all? I suppose I shouldn’t Copy the Box because that might allow multiple threads to access the Box concurrently, but it should be possible to get to the Box within the scope of the Mutex right?

(Github here: https://github.com/tinco/entity_rust/blob/master/src/events/mod.rs#L25)


#2

You are close, I think what you actually want is to declare the value in HashMap as a Vec<Box<Any+'static+Sync>>>. The items inside the vector are boxed because you don’t know the size ahead of time.

lazy_static! {
	pub static ref EventQueues: SharedMutex<HashMap<String, Vec<Box<Any+'static+Sync>>>> = SharedMutex::new(HashMap::new());
}

pub fn event_queue_push<T>(event_name: &String, event: Box<T>) where T: Any+'static+Sync {
	let mut map = EventQueues.write().unwrap();
	let mut vec = map.entry(event_name.clone()).or_insert(Vec::new());
	vec.push(event);
}

I also suspect you might want to use LinkedList instead of Vec. LinkedLists usually perform better if you don’t know the size of the list ahead of time. I also changed event to be Box but you could box it in the method if you would prefer.

My other suggestion, and this is purely a personal choice, is to pass the event_name in as a &str. So you would have:

pub fn event_queue_push<T>(event_name: &str, event: Box<T>) where T: Any+'static+Sync {
	let mut map = EventQueues.write().unwrap();
	let mut vec = map.entry(String::from(event_name)).or_insert(Vec::new());
	vec.push(event);
}

If you have a String object foo you want to pass in you would just pass &foo and the correct object is passed in. That way passing in a static string is simpler also you would just pass in “foo” instead of having to do String::from("foo") in the call.


#3

Thanks! I get how it makes it easier to push values on like that but I actually really want the Vecs to be properly typed so they have good locality. If it ever comes to performance optimization I’d be looking at presetting capacities and reusing the Vecs across handler invocations.

Why is it harder to do this when the Vec is the Any?


#4

Okay I think I understand more what you are trying to do…The problem is the let mut entry = *map... would move the object outside of the reference. I created a fragment that is basically trying to do what you are doing but that I can test in playpen:

use std::any::Any;
use std::collections::HashMap;

pub fn event_queue_push<T>(
    mut map : HashMap<String, Box<Any+Sync+'static>>,
    event_name: &String, 
    event: T
    ) where T: Any+'static+Sync
{
	let mut entry = map.entry(event_name.clone()).or_insert(Box::new(Vec::<T>::new()));
	let mut vec = (entry as &mut Box<Any+'static>).downcast_mut::<Vec<T>>().unwrap();
	vec.push(event);
}

fn main() {
}

Unfortunately it don’t work. The Sync specified HashMap values is just getting in the way, I’m getting:

error: non-scalar cast: `&mut Box<core::any::Any + Sync>` as `&mut Box<core::any::Any + 'static>`

And now we have no gone beyond my knowledge. If I remove the Sync on the HashMap value (not T) then this will compile.


#5

Thanks! That snippet was very helpful. The venerable @bluss helped me out on IRC to cover the last step. The following snippet compiles, also when ported back into the context of my package:

pub fn event_queue_push<T>(
    mut map : HashMap<String, Box<Any+Sync+'static>>,
    event_name: &String, 
    event: T
    ) where T: Any+'static+Sync
{
	let mut entry = map.entry(event_name.clone()).or_insert(Box::new(Vec::<T>::new()));
	let mut vec = (entry as &mut Any).downcast_mut::<Vec<T>>().unwrap();
	vec.push(event);
} 

I’ll now implement the getter so I can unit test this :slight_smile:

edit: This code is incorrect, see below


#6

I think it has to be &mut **entry as &mut Any but I actually don’t know how the coercions prioritize there quick test to confirm: yes it has to be &mut **.


#7

Woops yes I had the old version still in my clipboard. I’ve got the full getter and setter tested and working now here: