Reputation: 247
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
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