Mutable borrow in nested loops

I need to borrow values in the vector (particle_vec) and change their properties, but I can't borrow them as mutable.

(Note: I am not quite sure if I should use RefCell. Please forgive my lack of knowledge about these functionalities.)

pub struct Space(RefCell<Vec<Particle>>);
pub fn gravity_force(&mut self) {
        let Space(particle_vec) = self;
        let borrow_p = particle_vec.borrow_mut();

        for p in borrow_p.iter() {
            for q in borrow_p.iter() {
                if p == q {
                    continue;
                }

                let x_distance = p.coord.0 - q.coord.0;
                let y_distance = p.coord.1 - q.coord.1;                
                let distance = ((x_distance.pow(2) + y_distance.pow(2)) as f32).sqrt() as i16;
                let force_quantity_x = (x_distance/distance)*((p.mass*q.mass)/(distance as u16)) as i16;
                let force_quantity_y = (y_distance/distance)*((p.mass*q.mass)/(distance as u16)) as i16;

                p.apply_force((force_quantity_x, force_quantity_y));
                q.apply_force((force_quantity_x, force_quantity_y));
            }
        }
    }

Particle struct looks like this:

// Imaginary, point-like particles
#[derive(Clone)]
pub struct Particle {
    // universal mass unit (umu)
    mass: u16,
    // universal distance unit (udu)
    coord: (i16, i16),
    // time unit: universal time unit (utu)
    // composes universal time frame (utf, which is equal to one loop cycle)
    velocity: (i16, i16),
    // unique particle identification number
    id: u16
}

apply_force changes their velocity and that is, essentially, what I need in this function. I can't make borrow checker understand that I don't need to borrow the same element in the same vector but different elements in the same vector.

Any kind of help would be appreciated.

If you can always get &muts when you need to mutate, you don't need shared mutability like RefCell. So if you can always get a &mut Space when you need to mutate, you don't need the RefCell around the Vec<Particle>.

You could fix the example by putting a RefCell around the Particles inside the Vec<_>. Or, given what the Particles are made of, you could make them Copy and temporarily opt in to shared mutation. Or, you could use something besides two overlapping exclusive iterators (borrow_p.iter_mut()) to visit all the pairs of particles.

Here's one way that last possibility could be implemented.

    pub fn gravity_force(&mut self) {
        for p_idx in 0..self.0.len() {
            let (head, tail) = self.0.split_at_mut(p_idx);
            let [p, tail @ ..] = tail else { continue };
            for q in head.into_iter().chain(tail) {
                // ...
            }
        }
    }

Sorry, this will not really help you with your borrowing problems, but are your sure your nested loops make sense? For example, you calculate the interaction of particle 1 with particle 2, and also interaction of particle 2 with particle 1, which is the same.

2 Likes

Oh, I missed it, thanks for pointing out.

Maybe, I should have studied the structures in Rust a bit more before trying to make that "imaginary particle simulation thing".

I maintained your double-calculation logic in my first reply. The loops can be simplified if you don't need it.

        for p_idx in 0..self.0.len() {
            let Some([p, tail @ ..]) = self.0.get_mut(p_idx..) else {
                continue;
            };
            for q in tail {

Incidentally itertools probably has methods for both approaches (but I didn't bother looking).

Hey, you have to learn somehow. I'd say keep going if you're not frustrated and ask questions when you need to.

2 Likes