Search code examples
for-looprustimmutabilitymutableborrowing

How can I pass a Vector inside a for loop that is iterating through the Vector


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.


Solution

  • 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: