I understand the error, but I don't know how to go about it the correct way. It's is saying that I cant borrow self again even though its an immutable borrow. players is a Vec of Players and I want to be able to go thru each one to determine the nearest to each. Error is on line 178. Code can be found here:
The simplest way is to avoid using iter_mut. You can use a direct index, like the following:
pub fn run_simulation(&mut self) -> i32 {
while self.players.len() > 1 {
for i in 0..self.players.len() {
let target_idx = self.get_nearest_index(i);
let target_loc = self.players[target_idx].loc;
let player = &mut self.players[i];
if player.in_range(&target_loc) {
info!("{} is in range of {}", player.name, self.players[target_idx].name);
} else {
player.move_towards(&target_loc);
info!("{} is moving towards {}", player.name, self.players[target_idx].name);
}
}
}
self.turns
}
I tried this with using indexes, I am still getting:
cannot borrow self.players as mutable because it is also borrowed as immutable on the line with the call to damage():
self.players[i].damage(&mut self.players[target_idx], rng);
pub fn run_simulation(&mut self, rng: &mut ThreadRng) -> i32 {
while self.players.len() > 1 {
for i in 0..self.players.len() {
let target_idx = self.get_nearest_idx(&self.players[i]).unwrap();
if self.players[i].in_range(&self.players[target_idx].loc) {
info!("{} is in range of {}", self.players[i].name, self.players[target_idx].name);
if self.players[i].attack(&self.players[target_idx], rng) {
self.players[i].damage(&mut self.players[target_idx], rng);
if self.players[target_idx].is_dead() {
warn!("{} defeated {}", self.players[i].name, self.players[target_idx].name);
self.players.remove(target_idx);
}
}
}
else {
info!("{} is moving towards {}", self.players[i].name, self.players[target_idx].name);
}
}
self.turns += 1;
if self.turns > MAXTURNS {
warn!("Battle is taking too many turns: {}", self.turns);
break;
}
}
self.turns
}
Unfortunately, this is a stubborn deliberate limitation of the language.
&mut self doesn't just mean mutable, it means strictly exclusive, and it always applies to all of self, and all the fields reachable from it. By design borrow checking is not smart across function boundaries and applies maximally restrictive interpretation, even if the function body doesn't need it.
The exclusivity means that nothing else anywhere, no other argument, no other variable, will have any data shared with self for as long as &mut self can be used. This is a very valuable property in complex cases and multi-threaded code, but obviously frustrating when combining loops, getters, and other helper methods.
You can't use &mut self here.
- Move the code to a separate function that isn't using
self - Split the struct so that
&mut selfmethods never get arguments borrowing from themselves - Use
&selfand mutate throughRefCell - Queue up mutations to be applied later (but don't queue them up in form of references)
The first fix is ensuring get_nearest returns an exclusive reference to Player not a shared reference seeing how Player::damageneeds to mutate it. The second change I'd do is change Game::players to be a VecDeque instead of a Vec since you won't be able to hold onto a reference while also mutating the collection.
The above changes give something like the following:
pub fn get_nearest(&mut self, source: &Player) -> Option<(usize, &mut Player)> {
let mut min_distance = f32::MAX;
let mut target = None;
for (idx, player) in self.players.iter_mut().enumerate() {
if source.name != player.name {
let distance = source.loc.distance(&source.loc);
if distance < min_distance {
min_distance = distance;
target = Some((idx, player));
}
}
}
target
}
pub fn run_simulation(&mut self, rng: &mut ThreadRng) -> i32 {
while self.players.len() > 1 {
let player = self.players.pop_front().unwrap();
let (idx, nearest_player) = self.get_nearest(&player).unwrap();
if player.in_range(&nearest_player.loc) {
info!("{} is in range of {}", player.name, nearest_player.name);
if player.attack(nearest_player, rng) {
_ = player.damage(nearest_player, rng);
if nearest_player.is_dead() {
warn!("{} defeated {}", player.name, nearest_player.name);
drop(self.players.remove(idx));
}
}
} else {
info!("{} is moving towards {}", player.name, nearest_player.name);
}
self.players.push_back(player);
self.turns += 1;
if self.turns > MAX_TURNS {
warn!("Battle is taking too many turns: {}", self.turns);
break;
}
}
self.turns
}
One thing that may confuse you is this part:
if nearest_player.is_dead() {
warn!("{} defeated {}", player.name, nearest_player.name);
// Why are we allowed to mutate `self.players`
// while `nearest_player`, a `&mut Player` from
// `self.players`, exists?
drop(self.players.remove(idx));
}
Before non-lexical lifetimes (NLL), the above wouldn't compile; however with NLL, it is fine since we don't ever "use" nearest_player after calling self.players.remove.
This indeed works, thank you very much. Going to spend the weekend to try to understand the ownership and access issues this resolved.
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.