Bernd
Bernd

Reputation: 247

How to circumvent "cannot borrow `*self` as mutable more than once at a time" in a performant way for agent based simulation?

I would like to implement an agent-based simulation in Rust, but I run into the borrow checker.

Agents should live in a mutable grid, carrying a state in each cell. Each agent carries some mutable state. Usually, I would implement a collection of agents, e.g. as a HashMap from grid position to agent. In a simulation step, I would iterate over all agents and then update the agent's state in dependence on its own state, the state of the grid at this position, and the state of other agents close by.

A made-up example could look something like this:

use std::collections::HashMap;

struct Agent { // each agent carries some state
    id: i64,
    state: i32,
}

struct CellState { // some state of a grid cell
    state: i64,
}

struct Chart {
    agents: HashMap<usize, Agent>,
    grid: Vec<CellState>,
}

impl Chart {
    fn new(size: usize) -> Chart {
        let mut agents = HashMap::new(); // generate hash and populate with 2 agents
        agents.insert(10, Agent { id: 1, state: 1 });
        agents.insert(11, Agent { id: 2, state: 0 });
        let mut grid: Vec<CellState> = Vec::with_capacity(size);

        Chart {
            agents: agents,
            grid: grid,
        }
    }

    fn do_stuff(&mut self, agent: &mut Agent) {
        // here we want to update the state of agent,
        // based on the state of other agents in the grid
    }

    fn step_agents(&mut self) {
        for (_, agent) in &mut self.agents {
            self.do_stuff(agent);
        }
    }
}

fn main() {
    let mut ch = Chart::new(128);
    ch.step_agents();
}

This code produces the error

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:37:13
   |
36 |         for (_, agent) in &mut self.agents {
   |                           ----------------
   |                           |
   |                           first mutable borrow occurs here
   |                           first borrow later used here
37 |             self.do_stuff(agent);
   |             ^^^^ second mutable borrow occurs here

I understand the error and why the Rust compiler has a problem. What I do not understand is how to circumvent this problem in a performant way.

If I immutably borrow a reference to the agent, I cannot update its state. In a real example an agent would carry quite some state so cloning would not be cheap.

What would be the idiomatic Rust way to implement this?

Upvotes: 1

Views: 174

Answers (1)

Jason
Jason

Reputation: 5575

Having multiple exclusive references, in this case to the same HashMap, does indeed violate the constraints of the borrow checker.

Given that Agent apparently is not cheap to copy, I think you might want to look into wrapping Agent with std::cell::RefCell to dynamically borrow the values.

Here's a quick example:

use std::cell::RefCell;
use std::collections::HashMap;

#[derive(Debug, PartialEq)]
struct Agent {
    id: i64,
    state: i32,
}

struct CellState {
    state: i64,
}

struct Chart {
    agents: HashMap<usize, RefCell<Agent>>,
    grid: Vec<CellState>,
}

impl Chart {
    fn new(size: usize) -> Self {
        let mut agents = HashMap::new();
        agents.insert(1, RefCell::new(Agent { id: 1, state: 0 }));
        agents.insert(2, RefCell::new(Agent { id: 2, state: 1 }));

        let mut grid: Vec<CellState> = Vec::with_capacity(size);

        Self { agents, grid }
    }

    fn do_stuff(&self, agent: &RefCell<Agent>) {
        for other in self.agents.values().filter(|&other| agent != other) {
            if other.borrow().state == 1 {
                agent.borrow_mut().state += 1;
            }
        }
    }

    fn step_agents(&self) {
        for agent in self.agents.values() {
            self.do_stuff(agent);
        }
    }
}

fn main() {
    let mut chart = Chart::new(128);
    chart.step_agents();
    
    for agent in chart.agents.values() {
        println!("{:?}", agent);
    }
}

As the HashMap is visited in arbitrary order, the above could then return:

RefCell { value: Agent { id: 1, state: 1 } }
RefCell { value: Agent { id: 2, state: 1 } }

Upvotes: 2

Related Questions