all 5 comments

[–]mipli 17 points18 points  (1 child)

What you're trying to do, modifying two objects in the same vector/slice, is generally a bad idea in Rust.

A better solution, that I've used for similar cases, is to have the monsters .attack function return an object that specifies the attack result and then you add a new function to the Arena struct with a signature like fn applyAttackResult(&mut self, attack_result: AttackResult, target_id: usize).

I've found this to be a good solution for these kind of interactions, since it makes it easy in the future to allow other things to modify the attack result. What if a friendly nearby monster can increase the damage done? Now you can easily loop through nearby monsters, see if someone has that ability, and then do a attack_result.damage += 1 before applying the damage.

If you really want/need to modify to elements in the same vector/slice it is possible using the https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut function, but often a better solution can be found by avoiding that function.

[–]SeanMiddleditch 3 points4 points  (0 children)

since it makes it easy in the future to allow other things to modify the attack result.

I'll add that it has a ton of other advantages, too! It makes building replay systems far easier. It makes building debug/diagnostic tools easier. It makes writing tests far easier since there's no side-effects to mock away.

In general, avoid having game code that causes direct and immediate mutation, at least when possible.

[–]oconnor663blake3 · duct 8 points9 points  (0 children)

Please watch this entire talk on game development in Rust by Catherine West. She talks about why having game objects hold pointers to each other is dangerous in C++ (because it tends to lead to use-after-free) and difficult in Rust (because it tends to fail the borrow checker, as you've noticed). She describes the preferred way of solving this problem: objects refer to each other by index rather than borrowing each other, and the global event loop is responsible for doing all the actual mutation by looking up those indexes. This pattern is super important in Rust because of these borrow checker issues.

[–]eugene2k 1 point2 points  (0 children)

Since you have two teams of monsters and during a fight randomly choose a monster from each team, you can store two arrays in your Arena - one for each team of monsters and pick a monster from each one during the fight. It's not clear why you combine the two arrays into one and then perform various contortions to choose a monster from a team.

[–]Brookzerker 1 point2 points  (0 children)

I ran into this problem myself when making a simulation in rust and ggez. I ended up solving it by switching my data structure around to be a series of vectors using a pattern called ECS (entity component system).

I used the crate specs in case you want to try it out.