pub fn run_systems(mut self) {
for mut agent in self.agents.iter_mut() {
for mut system_id in agent.systems.iter_mut() {
let system = self.systems.get(system_id).unwrap();
system.simulate(&mut agent, &mut self);
}
}
}
I get the following error on the system.simulate line:
cannot borrow `agent` as mutable more than once at a time
second mutable borrow occurs here`. Apparently, iterating over the arrays is a borrowing operation.
I have also tried not using the iter function and iterating directly on owned values (not sure what that does):
pub fn run_systems(mut self) {
for mut agent in self.agents {
for mut system_id in agent.systems {
let system = self.systems.get(&system_id).unwrap();
system.simulate(&mut agent, &mut self);
}
}
}
But as soon as I reference &system_id, it detects an implicit borrowing in the for mut system_id in agent.systems { line
borrow of partially moved value: `agent`
partial move occurs because `agent.systems` has type `Vec<String>`, which does not implement the `Copy` traitrustcE0382
lib.rs(72, 34): `agent.systems` partially moved due to this implicit call to `.into_iter()`
collect.rs(233, 18): this function takes ownership of the receiver `self`, which moves `agent.systems`
I have tried all sorts of ways to write this code but can't find a way that works. How can I iterate over those values while also being able to pass mutable references to their content to other functions?
The issue lies in one potential issue with your code:
What happens if system.simulate() tries to access the agent.systems?
(e.g., adding or removing systems)
Given how Rust implements most collections, modifying a collection with a concurrent iteration over it could cause memory unsafety and is thus not allowed (hence the &mut borrow overlap error). As a bonus, even if a collection were made that would allow concurrent mutation of parts of it while iterating, in practice, this would easily lead to buggy code / logic errors, so this is yet another instance where Rust design is definitely preventing issues.
Now, to tackle the case where system.simulate() does not do anything with agent.systems whatsoever:
In that case, this is hinting at your code potentially needing to be refactored: your current agent would rather be an agent_with_systems, having, at least, two fields: agent and systems.
Then, you would:
for system_id in agent_with_systems.systems... {
...
system.simulate(&mut agent_with_systems.agent, ...)
}
This would be the cleanest way to express this property in a compile-time / statically checked (and thus robust) fashion
Otherwise, since refactoring is not always an option (may require too much of an effort), the usual work around is to take() / replace() the systems with a dummy one for the time of the iteration. If system.simulate() truly does not access that field, then all will be good. But if it does, it will be iterating with the dummy placeholder, and logic bugs will ensue
use ::std::mem::take;
let mut systems = take(&mut agent.systems); // replace(&mut agent.systems, <_>::default())
for system_id in systems.iter_mut() {
...
system.simulate(&mut agent, ...)
...
}
// Don't forget to put it back!
agent.systems = systems;
Regarding the "don't forget to put it back!" (which won't work if there is an early return, such as a ?, or a panic), it is way more robust to use drop glue / a defer statement to guard against this:
use ::std::mem::take;
let mut systems = take(&mut agent.systems); // replace(&mut agent.systems, <_>::default())
+ // Don't forget to put it back!
+ let mut deferred = ::scopeguard::guard((agent, systems), |(agent, systems)| {
+ agent.systems = systems;
+ });
+ let (agent, systems) = &mut *deferred;
for system_id in systems.iter_mut() {
...
system.simulate(&mut agent, ...)
...
}
- // Don't forget to put it back!
- agent.systems = systems;
Incidentally, it is interesting to notice that deferred's state is basically agent_with_systems...
That makes sense, what I've done for now is copied the systems vec and iterating over the copy to make things simpler, as it's a small one.
But I get the same problem with the outer loop which is iterating on self.agents. I can't pass the vector of agents to my simulate function because it's being iterated on...