Borrow as mutable more than once at a time


#1

Hello fellow rust users. The very same as last time I maneuvered myself into a corner and can’t escape. I know the “borrow as mutable”-problem comes up many times. Please bear with me.

I get two error messages. I understand them, but I’m unsure how to remedy them. I’ve prepared an example below.

I would be happy if someone could help me fix the errors. And also I’d like to hear further code suggestions.

Thanks! I’ve prepped a gist here

The errors:

error[E0499]: cannot borrow `game_runtime.objects` as mutable more than once at a time
   --> src/main.rs:106:22
    |
103 |                     let mut objects: Vec<&mut Box<dyn Machine + Send + Sync>> = gather(requested_ids, &mut game_runtime.objects);
    |                                                                                                            -------------------- first mutable borrow occurs here
...
106 |                     let my_object = game_runtime.objects.get_mut(&id).unwrap();
    |                                     ^^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
...
126 |                 }
    |                 - first borrow ends here

error[E0499]: cannot borrow `*objects` as mutable more than once at a time
   --> src/main.rs:179:9
    |
179 |         match objects.get_mut(&id) {
    |               ^^^^^^^ mutable borrow starts here in previous iteration of loop
...
187 | }
    | - mutable borrow ends here

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0499`.

The code:

use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::thread;
use std::time::{Duration};
pub type TICK= u64;

fn main() {
	let mut game_runtime = GameRuntime { objects: HashMap::new(), chunks: HashMap::new() };

	//create a player
	//in reality load data from database
	let player_id = 1;
	let o = Player::new(player_id);
	let chunk_position = Position(o.position.0 / 100, o.position.1 / 100, o.position.2 / 100);//dummy space partition

	//insert positions into game_runtime.chunks
	// which is: HashMap<space partitioned position, HashMap<ID, real-Position>>
	match game_runtime.chunks.entry(chunk_position) {
		Entry::Occupied(position_map) => {
			position_map.into_mut().insert(player_id, chunk_position);
		}
		Entry::Vacant(position_map) => {
			let mut map = HashMap::new();
			map.insert(player_id, o.position);
			position_map.insert(map);
		}
	}

	//insert player state machine
	game_runtime.objects.insert(player_id, GameObject {
		state_machine: Box::new(o),
	});
	//create player END



	//create a enemy
	//in reality load data from database
	let enemy_id = 2;
	let o = Enemy::new(enemy_id);
	let chunk_position = Position(o.position.0 / 100, o.position.1 / 100, o.position.2 / 100);//dummy space partition

	//insert positions into: HashMap<space partitioned position, HashMap<ID,real-Position>>
	match game_runtime.chunks.entry(chunk_position) {
		Entry::Occupied(position_map) => {
			position_map.into_mut().insert(enemy_id, chunk_position);
		}
		Entry::Vacant(position_map) => {
			let mut map = HashMap::new();
			map.insert(enemy_id, o.position);
			position_map.insert(map);
		}
	}

	//insert enemy state machine
	game_runtime.objects.insert(enemy_id, GameObject {
		state_machine: Box::new(o),
	});
	//create enemy END




	let mut tick_counter: u64=0;

	//each tick advance state machine with ID
	let mut tick_scheduling_map: HashMap<TICK, Vec<ID>> = HashMap::new();

	//test data: on tick 5 wake up object with id 1
	tick_scheduling_map.insert(5, vec![1]);

	thread::spawn(move || loop {

		//for next loop iteration
		let mut next: HashMap<TICK, Vec<ID>> = HashMap::new();
		//this entire part is soo deeply nested. Is it possible to extract the mini-jobs into separate functions and let it still compile? Currently it doesn't.
		match tick_scheduling_map.entry(tick_counter) {
			Entry::Occupied(vec) => {
				for id in vec.get().iter() {
					let requested_ids;

					//get current state machine
					match game_runtime.objects.get_mut(&id) {
						Some(ref mut object_by_id) => {
							match object_by_id.state_machine {
								ref mut my_object => {
									println!("on tick {} wake up {}", tick_counter, id);
									//on tick returns a list of enemies (ids) to battle against
									match my_object.schedule(tick_counter, &game_runtime.chunks) {
										Some(_requested_ids) => {
											//save list of enemies
											requested_ids = _requested_ids;
										}
										None => { requested_ids = vec![]; }
									}
								}
							}
						}
						None => { requested_ids = vec![]; }
					}

					//now gather all requested enemies
					let mut objects: Vec<&mut Box<dyn Machine + Send + Sync>> = gather(requested_ids, &mut game_runtime.objects);

					//but get current state machine again in order to call trigger_event on it
					let my_object = game_runtime.objects.get_mut(&id).unwrap();

					//combine both
					match my_object.state_machine.trigger_event(tick_counter, &mut objects) {
						//trigger_event returns integer >=1 specifying when to reschedule
						Some(rescheduled_at_tick) => {
							if rescheduled_at_tick >= 1 {
								let next_tick = tick_counter + rescheduled_at_tick;
								match next.entry(next_tick) {
									Entry::Vacant(id_vec) => {
										id_vec.insert(vec![*id]);
									}
									Entry::Occupied(mut id_vec) => {
										id_vec.get_mut().push(*id);
									}
								};
							}
						}
						None => {}
					}
				}
				vec.remove_entry();
			}
			Entry::Vacant(_v) => {}
		}

		//update map for next schedules
		for (tick, id_vec) in &next {
			match tick_scheduling_map.entry(*tick) {
				Entry::Vacant(vec) => {
					vec.insert(id_vec.to_vec());
				}
				Entry::Occupied(mut vec) => {
					vec.get_mut().extend(id_vec.to_vec());
				}
			};
		}

		//remove current tick schedules
		tick_scheduling_map.remove(&tick_counter);

		tick_counter+=1;

		thread::sleep(Duration::from_millis(1000));
	});
}

pub trait Machine {
	fn schedule(&mut self, current_tick: u64, chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>;
	fn trigger_event(&mut self, tick_counter: u64, objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>;
	fn get_position(&self) -> Position;
	fn set_position(&mut self, position: Position);
	fn get_id(&self) -> ID;
}

impl std::fmt::Debug for 'static + Machine + std::marker::Send + std::marker::Sync {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "Machine at {:?} with id: {}",self.get_position(),self.get_id())
	}
}

pub struct GameRuntime {
	pub objects: HashMap<ID, GameObject>,
	pub chunks: HashMap<Position, HashMap<ID, Position>>,
}

pub struct GameObject {
	pub state_machine: Box<dyn Machine + Send + Sync>,
}

fn gather(requested_ids: Vec<u64>, objects: &mut HashMap<ID, GameObject>) -> Vec<&mut Box<dyn Machine + Send + Sync>> {
	let mut out = vec![];
	for id in requested_ids.iter() {
		match objects.get_mut(&id) {
			Some(mut object_by_id) => {
				out.push(&mut object_by_id.state_machine);
			}
			None => {}
		}
	}
	out
}

#[derive(Debug, Clone)]
#[derive(PartialEq, Eq, Hash)]
#[derive(Default)]
#[derive(Copy)]
pub struct Position(pub i64, pub i64, pub i64);

pub type ID = u64;

pub struct Player {
	pub id: u64,
	pub position: Position,
}

impl Player {
	pub fn new(id: u64) -> Self {
		Player {
			position: Position(5, 1, 0),
			id,
		}
	}
}

impl Machine for Player {
	fn schedule(&mut self, _current_tick: u64, _chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>{
		Some(vec![1])//finds nearest neighbors via space partition and distance and returns id, in this case battle against object with id 1
	}

	fn trigger_event(&mut self, _tick_counter: u64, _objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>
	{
		//... handle internal event here
		//for example
		for mut member in _objects.iter_mut() {//do something -- anything -- with the objects and self
			let current_position = member.get_position();
			member.set_position(Position(current_position.0-1,current_position.1+1,current_position.2));
		}

		None//then do not reschedule event
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn get_position(&self) -> Position{
		self.position
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn set_position(&mut self, position: Position){
		self.position=position;
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn get_id(&self) -> ID{
		self.id
	}
}


pub struct Enemy {
	pub id: u64,
	pub position: Position,
}

impl Enemy {
	pub fn new(id: u64) -> Self {
		Enemy {
			position: Position(0, 0, 0),
			id,
		}
	}
}

impl Machine for Enemy {
	fn schedule(&mut self, _current_tick: u64, _chunks: &HashMap<Position, HashMap<ID, Position>>) -> Option<Vec<u64>>{
		None
	}

	fn trigger_event(&mut self, _tick_counter: u64, _objects: &mut std::vec::Vec<&mut std::boxed::Box<(dyn Machine + std::marker::Send + std::marker::Sync + 'static)>>) -> Option<u64>
	{
		//... handle internal event here

		None//then do not reschedule event
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn get_position(&self) -> Position{
		self.position
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn set_position(&mut self, position: Position){
		self.position=position;
	}

	//must be repeated for every object impl, sadly -- is there a way out?
	fn get_id(&self) -> ID{
		self.id
	}
}

!!! Thanks


#2

Your objects should be Rc<RefCell<GameObject>>.

Without RefCell borrowing even one object for mutation will put an exclusive write lock on entire game_runtime.

Without Rc it could be doable, but still you’d have to be careful, because shared borrow or any object would put shared read-only lock on entire game_runtime.


#3

Wow, thank you, @kornel. That did it (after some heavy refactoring for a newbie like me). Here’s the new gist (with Rc). It also works without Rc, so I’m using that.

I’m using a trait object Machine to store different kinds of “structs” in a hashmap. My hidden question in the code “//must be repeated for every object impl, sadly – is there a way out?” is still open. Is there another way? Let’s assume I not only have Player and Enemy, but 100 of these definitions. But each time I have to implement

fn get_position(&self) -> Position;
fn set_position(&mut self, position: Position);
fn get_id(&self) -> ID;

and alike for these types. It does not look comfortable to me. fn schedule and trigger_event have to be re-implemented also every time, but these will indeed differ for all types.


#4

You can write a macro to implement this.

Another option is to have explicit field for common properties, so you can pass &foo.common_properties when you need to (i.e. using common struct as if it was a base class)


#5

If you can be certain (or enforce via code/assertions) that my_object is not present in objects and that requested_ids has no duplicates, then you can consider using a bit of unsafe code to get the mutable borrows.


#6

Hi vitalyd,
yes, I think it is no problem to make sure that there are no duplicates and that my_object is not present in objects. I googled a little and found an internals link multiple-mutable-borrows-finer-grained-borrow-check requesting some further annotations to allow mutable borrows.
I think there is some run-time overhead using RefCell which could be a problem for me later. I’m happy it works now though.

I never wrote unsafe code in rust. Can you give me an example how to get the mutable borrows via unsafe?

Hi kornel, I’ll try. Thank you!


#7

Example - note I didn’t add any assertions, this just shows the unsafe incarnations.


#8

Thank you, vitalyd. Can you take a quick look at my change: As in my second gist I refactored a little, instead of gathering dyn Machine + Send … directly I gather the parent GameObject as in this gist (and put the initializations directly in the spawned thread). I ran into another error then

error[E0606]: casting `&mut &mut GameObject` as `*mut GameObject` is invalid
   --> src/main.rs:216:14
    |
216 |                 let sm = &mut object_by_id as *mut _;
    |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0606`.

and fixed it. I changed

		match objects.get_mut(&id) {
			Some(mut object_by_id) => {
				let sm = &mut object_by_id as *mut _;
				out.push(unsafe { &mut *sm });
			}
			None => {}
		}

to (removing the muts)

		match objects.get_mut(&id) {
			Some(object_by_id) => {
				let sm = object_by_id as *mut _;
				out.push(unsafe { &mut *sm });
			}
			None => {}
		}

Is this equally still safe?


#9
match objects.get_mut(&id) {
			Some(object_by_id) => {
				let sm = object_by_id as *mut _;
				out.push(unsafe { &mut *sm });
			}
			None => {}
		}

Yeah, the above is fine - object_by_id is already a &mut GameObject because HashMap::get_mut() returns an Option<&mut V> and when you pattern match that, object_by_id is the &mut V (V=GameObject for your case).

As for the “Is this equally still safe” question, the safety of your unsafe usage is entirely (from what I can tell) predicated on you never having 2+ mutable references to the same GameObject (or parts thereof while having a GameObject mutable reference).


#10

Then consider this case solved :grinning:. My thanks to you all for your help!