Im trying to write some code in rust but im stuck on the borrow checker. I have a vector of plant structs and want to run on every plant the update function that checks for collision.
This is programmed the way i did in python or c#. As far as i understand the problem is that i cant borrow game.plants[p]
and the also pass the &game.plants
as second borrow. I assume this is to prevent iteration error that happen is i change the vec<Plant>
while iterating over it. In c# this always results in a crash.
error[E0499]: cannot borrow `game.plants` as mutable more than once at a time
--> src/main.rs:143:50
|
143 | game.plants[p].update(&mut game.map, &mut game.plants, &game.plant_data);
| ----------- ------ ^^^^^^^^^^^^^^^^ second mutable borrow occurs here
| | |
| | first borrow later used by call
| first mutable borrow occurs here
Here i call on every plant the update and pass the map(no problem), the vector i´m iterating over to let the function know where all the plants are and the plant data which is just data and makes no problem
for p in 0..game.plants.len() {
game.plants[p].update(&mut game.map, &game.plants, &game.plant_data);
}
fn update(&mut self, map: &Map, plants: &Vec<Plant>, plant_data: &PlantData){
if self.check_plant_plant_collision(&plants){
self.grow(&map, &plant_data);
}
}
How do i check collisions in rust? I need to iterate the vector of plants and the collision function needs to know where the other plants are.
fn check_plant_plant_collision(&self, plants: &Vec<Plant>) -> bool {
for other_plant in plants {
if self.position != other_plant.position {
let distance = self.position.distance_to(other_plant.position);
let combined_radius = self.radius + other_plant.radius;
if distance < combined_radius {
return true; // Collision detected
}
}
}
false // No collision detected
}
Am i thinking too much in objects? Here are the used Structs.
struct Game{
map: Map,
plants: Vec<Plant>,
plant_data: PlantData,
}
struct Plant {
position: Vector2,
radius: f32,
color: Color,
}
struct PlantData{
initial_size: f32,
max_size: f32,
growth_rate: f32,
}
It seems not reasonable to copy the vector of plants to satisfy the borrow checker. And to clone the plant, do the checks and then set the vector index to the copied plant seems a strange way to do this. What is the rust way of doing this? Is the way i structure my data ok or will i run into a lot more problems this way?
There's many ways to fix this.
You can separate the mut and non-mut parts.
fn update_game2(game: &mut Game) {
for p in 0..game.plants.len() {
if game.plants[p].check_plant_plant_collision(&game.plants) {
game.plants[p].grow(&game.map, &game.plant_data);
}
}
}
While both game.plants[p]
use identical syntax, the first one creates &Plant
while the second one creates &mut Plant
, which is why they must be separate.
You can split the Vec<Plant>
into three parts.
fn update_game3(game: &mut Game) {
for p in 0..game.plants.len() {
let (begin, rest) = game.plants.split_at_mut(p);
let (current, end) = rest.split_first_mut().unwrap();
current.update2(&mut game.map, begin, end, &game.plant_data);
}
}
impl Plant {
fn update2(
&mut self,
map: &Map,
plants_begin: &[Plant],
plants_end: &[Plant],
plant_data: &PlantData,
) {
if self.check_plant_plant_collision(plants_begin)
|| self.check_plant_plant_collision(plants_end)
{
self.grow(map, plant_data);
}
}
}
This has the advantage of automatically skipping checking a plant against itself.
Another thing you can do is use interior mutability like Vec<Cell<Plant>>
, which allows you to modify the elements from a shared reference. Or you can use a temporary buffer and copy each Plant
to that, which has the advantage of your updates no longer being dependent on iteration order.