如何在遍历 Vector 的 for 循环中传递 Vector

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

我需要迭代一个可变向量,并且在 for 循环内部我还需要将向量传递给修改当前对象的函数。

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

因为存在行星的可变借用,所以不可能创建不可变值甚至另一个可变值,我看不出解决这个难题的方法。

您可以尝试在不应用借用规则的情况下使用索引来引用特定行星。而且你应该避免将行星的 vec 分成两部分(实际的和其他的)。

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();
    }
}

不幸的是,你不能轻易做到这一点,编译器只允许一次可变借用或同时多次借用。即使你觉得从生锈的角度来看这应该是合法的,但事实并非如此。

有几种修复方法:

  • 直接使用索引
  • 使用内部可变性
  • 有更好的解决办法来避免这个问题

在你的情况下,我认为使用索引是有意义的,因为你不想让当前的行星在其他行星上,所以我们可以改变向量以将当前行星放在最后并制作一个子切片其他行星:

#[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() 这会将我们的行星分成我们要处理的行星和其余行星。

参见: