1

I need to iterate over a mutable vector, and inside of the for loop I also need to pass the vector into a function that modifies the current object.

pub struct Vector2 {
    x: f64,
    y: f64,
}

pub struct Planet {
    position: Vector2,
    init_velocity: Vector2,
    curr_velocity: Vector2,
    radius: f64,
    mass: f64,
}

impl Planet {
    pub fn update_velocity(
        &mut self,
        other_planets: &Vec<Planet>,
        grav_constant: f64,
        timestep: f64,
    ) {
        for p in other_planets {
            // Calculate self's velocity relative to all other planets
        }
    }

    pub fn update_position(&mut self) {
        self.position.x = self.position.x + self.curr_velocity.x;
        self.position.y = self.position.y + self.curr_velocity.y;
    }
}

fn main() {
    let mut planets = Vec::<Planet>::new();
    planets.push(Planet {
        position: Vector2 { x: 10.0, y: 10.0 },
        init_velocity: Vector2 { x: 1.0, y: 1.0 },
        curr_velocity: Vector2 { x: 1.0, y: 1.0 },
        radius: 20.0,
        mass: 500.0,
    });
    for p in &mut planets {
        p.update_velocity(&planets, 0.0000000000674 as f64, 0.0);
        p.update_position();
    }
}
error[E0502]: cannot borrow `planets` as immutable because it is also borrowed as mutable
  --> src/main.rs:42:27
   |
41 |     for p in &mut planets {
   |              ------------
   |              |
   |              mutable borrow occurs here
   |              mutable borrow later used here
42 |         p.update_velocity(&planets, 0.0000000000674 as f64, 0.0);
   |                           ^^^^^^^^ immutable borrow occurs here

Because a mutable borrow of planets exists, it's not possible to make an immutable or even another mutable value and I can't see a way around this conundrum.

SarahGreyWolf
  • 23
  • 1
  • 5

2 Answers2

1

Unfortunately you can't easily do that, compiler only allow one mutable borrow or multiple borrow at the same time. Even if you feel like this should be legal from the point of view of rust it isn't.

There is several way to fix it:

  • use index directly
  • use interior mutability
  • have a better solution that avoid the problem

In your case I think use index make sense, because you don't want to have the current planets in other planet, so we can mutate the vector to put the current planet at the end and make a sub slice of other planets:

#[derive(Debug)]
pub struct Vector2 {
    x: f64,
    y: f64,
}

#[derive(Debug)]
pub struct Planet {
    position: Vector2,
    init_velocity: Vector2,
    curr_velocity: Vector2,
    radius: f64,
    mass: f64,
}

impl Planet {
    pub fn update_velocity(&mut self, other_planets: &[Planet], grav_constant: f64, timestep: f64) {
        println!("{:#?}", other_planets);
    }

    pub fn update_position(&mut self) {
        self.position.x = self.position.x + self.curr_velocity.x;
        self.position.y = self.position.y + self.curr_velocity.y;
    }
}

struct Guard<'a, T> {
    slice: &'a mut [T],
    a: usize,
    b: usize,
}

impl<'a, T> Guard<'a, T> {
    fn new(slice: &'a mut [T], a: usize, b: usize) -> Self {
        slice.swap(a, b);
        Self { slice, a, b }
    }

    fn split_last_mut(&mut self) -> Option<(&mut T, &mut [T])> {
        self.slice.split_last_mut()
    }
}

impl<'a, T> Drop for Guard<'a, T> {
    fn drop(&mut self) {
        self.slice.swap(self.a, self.b);
    }
}

fn main() {
    let mut planets = Vec::<Planet>::new();
    planets.push(Planet {
        position: Vector2 { x: 10.0, y: 10.0 },
        init_velocity: Vector2 { x: 1.0, y: 1.0 },
        curr_velocity: Vector2 { x: 1.0, y: 1.0 },
        radius: 20.0,
        mass: 500.0,
    });
    planets.push(Planet {
        position: Vector2 { x: 20.0, y: 20.0 },
        init_velocity: Vector2 { x: 2.0, y: 2.0 },
        curr_velocity: Vector2 { x: 2.0, y: 2.0 },
        radius: 40.0,
        mass: 1000.0,
    });
    planets.push(Planet {
        position: Vector2 { x: 40.0, y: 40.0 },
        init_velocity: Vector2 { x: 4.0, y: 4.0 },
        curr_velocity: Vector2 { x: 4.0, y: 4.0 },
        radius: 80.0,
        mass: 2000.0,
    });

    let len = planets.len();
    let last = len - 1;
    for i in 0..len {
        let mut g = Guard::new(&mut planets, i, last);

        let (p, other_planets) = g.split_last_mut().unwrap(); // can't fail

        p.update_velocity(&other_planets, 0.0000000000674 as f64, 0.0);
        p.update_position();
    }
}

Playground, Guard only exist to avoid mistake, the key function is split_last_mut() that will split our planets into the one we want to process and the rest.

See:

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • This works great thank you very much, only just starting in Rust so the whole borrow thing and immutable/mutable is still getting me in alot of binds that I'm slowly working my way through – SarahGreyWolf Apr 22 '20 at 00:06
0

You could try using indexes to refer to a specific planet without applying the borrowing rules. And you should avoid splitting a vec of planets into two parts (the actual and the others).

pub struct Vector2 {
    x: f64,
    y: f64,
}

pub struct Planet {
    position: Vector2,
    init_velocity: Vector2,
    curr_velocity: Vector2,
    radius: f64,
    mass: f64,
}

impl Planet {
    fn update_velocity_of_all_planets(
        self_idx: usize,
        planets: &mut Vec<Planet>,
        grav_constant: f64,
        timestep: f64,
    ) {
        for p in planets {
            // Do maths stuff with other planets
        }
    }

    pub fn update_position(&mut self) {
        self.position.x = self.position.x + self.curr_velocity.x;
        self.position.y = self.position.y + self.curr_velocity.y;
    }
}

fn main() {
    let mut planets = Vec::<Planet>::new();
    planets.push(Planet {
        position: Vector2 { x: 10.0, y: 10.0 },
        init_velocity: Vector2 { x: 1.0, y: 1.0 },
        curr_velocity: Vector2 { x: 1.0, y: 1.0 },
        radius: 20.0,
        mass: 500.0,
    });
    for idx in 0..planets.len() {
        Planet::update_velocity_of_all_planets(idx, &mut planets, 0.0000000000674 as f64, 0.0);
        planets[idx].update_position();
    }
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Mubelotix
  • 81
  • 4
  • why did you change to a mut vec ? – Stargateur Apr 21 '20 at 23:20
  • This solves the basic issue but at the end of the day I'll run into it again further down the line, I clarified the point of update_velocity in my code since it seems it was misunderstood, it's purpose is to update the velocity of self relative to all the other planets – SarahGreyWolf Apr 21 '20 at 23:45