我正在尝试编写一个程序,以均匀(或尽可能接近)分布关于球体表面的点。我试图通过在单位球体上随机放置N个点,然后运行多个步骤使点相互排斥来实现这一点。
问题出在点数组的循环中。下面的代码遍历每个点,然后在其中循环,再次遍历每个点并计算每个点对之间的排斥力。
for point in points.iter_mut() {
point.movement = Quaternion::identity();
for neighbour in &points {
if neighbour.id == point.id {
continue;
}
let angle = point.pos.angle(&neighbour.pos);
let axis = point.pos.cross(&neighbour.pos);
let force = -(1.0/(angle*angle)) * update_amt;
point.movement = point.movement * Quaternion::angle_axis(angle, axis);
}
}
我收到了错误:
src/main.rs:71:27: 71:33 error: cannot borrow `points` as immutable because it is also borrowed as mutable
src/main.rs:71 for neighbour in &points {
和解释
the mutable borrow prevents subsequent moves, borrows, or modification of `points` until the borrow ends
我是Rust的新手,来自C ++背景,我不知道如何在Rust中使用这种模式。
对此的任何解决方案都将非常感激,因为我现在绝对坚持想法。
答案 0 :(得分:8)
有几种方法可以写出来。
一个是你的建议,即将运动分成单独的向量,因为这确保了对运动值的可变借用不会强制编译器保守地禁止访问points
的其余部分以避免可变借用来自别名。在Rust中,&mut
引用永远不会别名:如果值只能通过一个路径访问,则可以保证任何/所有突变都是内存安全的,如果存在别名则需要花费更多精力(包括运行时检查)以确保突变是安全的。
另一种方法是使用外环的标记:
for i in 0..points.len() {
let mut movement = Quaternion::identity();
for neighbour in &points {
if neighbour.id == points[i].id {
continue
}
// ...
movement = movement * Quaternion::angle_axis(angle, axis);
}
points[i].movement = movement
}
第三种方法是改变循环的工作方式:在考虑点与其邻居之间的交互时,同时更新点和邻居。这允许人们“三角形”迭代:点0
与1..n
交互,点1
与2..n
交互,......(其中n = points.len()
)。这可以通过编译器理解的不会别名的方式完成。
首先必须将所有movement
重置为1
。然后主循环由一个选择单个元素的外部循环组成,然后可以“重新借用”内部循环的迭代器。外部循环不能使用for
,因为for
将取得迭代器的所有权,幸运的是,while let
允许人们整齐地写这个:
for point in &mut points {
point.movement = Quaternion::identity()
}
let mut iter = points.iter_mut();
while let Some(point) = iter.next() {
// this reborrows the iterator by converting back to a slice
// (with a shorter lifetime) which is coerced to an iterator
// by `for`. This then iterates over the elements after `point`
for neighbour in &mut iter[..] {
// `neighbour` is automatically distinct from `point`
let angle = point.pos.angle(&neighbour.pos);
let axis = point.pos.cross(&neighbour.pos);
let force = -(1.0 / (angle*angle)) * update_amt;
point.movement = point.movement * Quaternion::angle_axis(angle, axis);
neighbour.movement = neighbour.movement * Quaternion::angle_axis(angle, -axis);
}
}
NB。我相信axis
更新必须撤消neighbour
。 (我没有编译过这个,但希望它很接近。)
理论上,后者与目前为止的任何其他建议相比,提供了大约2倍的加速。至少,它减少了angle
,axis
&的计算次数。 force
从n * (n - 1)
到n * (n - 1) / 2
。
答案 1 :(得分:1)
我找到了自己问题的答案
for (point, movement) in points.iter().zip(movements.iter_mut()) {
*movement = Quaternion::identity();
for neighbour in &points {
if neighbour.id == point.id {
continue;
}
let angle = point.pos.angle(&neighbour.pos);
let axis = point.pos.cross(&neighbour.pos);
let force = -(1.0/(angle*angle)) * update_amt;
*movement = (*movement) * Quaternion::angle_axis(angle, axis);
}
}
通过创建一个单独的向量来保持运动,而不是让运动成为该点的特征,我可以将点向量借用两次作为不可变,并将运动向量借用一次作为可变。
我认为C ++思维方式仍然让我在课堂上思考得太多,但我仍然错误地认为这是一种理想的方法来解决这种生锈模式。如果有人对此解决方案有疑问,或者只是知道更好的方法,那么任何建议都会很棒。