Again, a borrowing problem

Hello,
I have a loan problem.
In the update function, I browse the vector '"Alien"
And I must also create lasers. Hence, compilation error second mutable borrow occurs here
There is probably a design problem in my way of doing things. But I do not see how to fix it.


pub struct Game {
 /* other data */
    pub hero: Hero,   
    pub lasers: Vec<Laser>,
    pub aliens: Vec<Alien>,
} 

impl Game{    
/*  other code */
    pub fn update(&mut self) {
    /* other code */
        for (_pos, alien) in self.aliens.iter_mut().enumerate() {
           if alien.chrono_shoot >= 60.0 {
                self.create_laser(
                        alien.sprite.x
                            + (alien.sprite.width as f32 * alien.sprite.scale) / 2.0,
                        alien.sprite.y,
                        0.0,
                        3.0
                    );
            }
            alien.update();
        }
    }

    fn create_laser(&mut self, x: f32, y: f32, vx : f32, vy : f32) {

        let mut laser = Laser::new();
        laser.sprite.x = x - laser.sprite.width as f32 / 2.0;
        laser.sprite.y = y - laser.sprite.height as f32 / 2.0;
        laser.vx = vx;
        laser.vy = vy;
        self.lasers.push(laser);

    }
/* other code */
}


error[E0499]: cannot borrow `*self` as mutable more than once at a time
   --> src/game/mod.rs:199:17
    |
12  |         for (_pos, alien) in self.aliens.iter_mut().enumerate() {
    |                              ----------------------------------
    |                              |
    |                              first mutable borrow occurs here
    |                              first borrow later used here
13  |            if alien.chrono_shoot >= 60.0 {
14  |                 self.create_laser(
    |                 ^^^^ second mutable borrow occurs here 

The problem is that &mut self argument gives exclusive access to all of self, including self.aliens, so technically self.create_laser could call something like self.aliens.clear() and derail the outer loop.

The borrow checker checks interfaces, not implementation. This is deliberate, because it allows libraries to modify internals of functions without risk of breaking anyone, but it also means that the borrow checker doesn't care that you're not actually using self.aliens in self.create_laser. You could, and that's not allowed.

There are two immediate ways to work around it:

  1. Create helper functions (static methods) that only get references to the fields they need, not all of self
  2. Use Mutex or RefCell to allow shared mutable access to some fields, and then use shared &self instead of exclusive &mut self.

The bigger fix is to use ECS pattern instead of arrays of big mutable objects:

5 Likes

Hello,

Sorry for the late response, I looked at the possibility of rewrite in ECS rather than pseudo OOP. Even if ECS does not have only advantages (they speak often of slowness, not a lot of tutorial). But it adapts well to the phylosophy of RUST (functional programming in this case)

Hi, ECS should not cause slowdown but speed-up.
Regarding tutorials once you understand the basics you can make anything you want.

Here's some links that might interest you: ecs-faq, Overwatch talk, Specs book, Evoli.

2 Likes

Hello,
I tried this and it compiles. The idea was to not take a self reference but as suggested by leudz to use static methods.

impl Game{    
/*  other code */
    pub fn update(&mut self) {
    /* other code */
        let ref mut aliens = self.aliens;
        let ref mut lasers = self.lasers;
        for (_pos, alien) in aliens.iter_mut().enumerate() {
           if alien.chrono_shoot >= 60.0 {                                        
                Game::create_laser(lasers,
                        alien.sprite.x
                            + (alien.sprite.width as f32 * alien.sprite.scale) / 2.0,
                        alien.sprite.y,
                        0.0,
                        3.0
                    );                    
            }
            alien.update();
        }
    }

    
    fn create_laser(lasers: &mut Vec<Laser>, x: f32, y: f32, vx : f32, vy : f32) {
        let mut laser = Laser::new();
        laser.sprite.x = x - laser.sprite.width as f32 / 2.0;
        laser.sprite.y = y - laser.sprite.height as f32 / 2.0;
        laser.vx = vx;
        laser.vy = vy;
        lasers.push(laser);
    }
}
1 Like

@Pent, I just tested the code you offered me, this walk in my game.

I looked at this 2nd solution to use ECS pattern.

@leudz, at first reading I made an error of interpretation, as regards the optimization at the processor level, the prediction of the instructions between the model OOP and ECS, or precisely it is very hard for the processor in OOP to predict the instruction unlike ECS or it's big series of loops.

On the other hand for ECS, there is not much information contrary to the OOP in C ++

Typically it is very easy to organize its code in C, C ++ or Rust a file class for struct (with header for C and C ++) but with ECS under rust for the moment I put everything in a single file and it becomes already too long for me.

If not, it's all quite logical reasoning.

You could do like Evoli, a "components" folder with ≈1 component per file. And a "systems" folder with ≈1 system per file.

You end up with small files but well organized.

I apply this organization. it makes me think of a C ++. and it's okay.