在嵌套循环中修改结构的Rust方法是什么?

时间:2015-04-02 13:27:03

标签: rust

给定的是一系列以彼此某种方式相互作用的物体。作为一个新手我接触它,因为我会用其他语言来做:

struct Body {
    x: i16,
    y: i16,
    v: i16,
}

fn main() {
    let mut bodies = Vec::<Body>::new();

    bodies.push(Body { x: 10, y: 10, v: 0 });
    bodies.push(Body { x: 20, y: 30, v: 0 });

    // keep it simple and loop only twice
    for i in 0..2 {
        println!("Turn {}", i);
        for b_outer in bodies.iter() {
            println!("x:{}, y:{}, v:{}", b_outer.x, b_outer.y, b_outer.v);
            let mut a = b_outer.v;
            for b_inner in bodies.iter() {
                // for simplicity I ignore here to continue in case b_outer == b_inner
                // just do some calculation
                a = a + b_outer.x * b_inner.x;
                println!(
                    "    x:{}, y:{}, v:{}, a:{}",
                    b_inner.x,
                    b_inner.y,
                    b_inner.v,
                    a
                );
            }
            // updating b_outer.v fails
            b_outer.v = a;
        }
    }
}

内循环完成后更新b_outer.v失败:

error[E0594]: cannot assign to immutable field `b_outer.v`
  --> src/main.rs:32:13
   |
32 |             b_outer.v = a;
   |             ^^^^^^^^^^^^^ cannot mutably borrow immutable field

使b_outer变为可变:

for b_outer in bodies.iter_mut() { ...

也不起作用:

error[E0502]: cannot borrow `bodies` as mutable because it is also borrowed as immutable
  --> src/main.rs:19:32
   |
16 |             for b_outer in bodies.iter() {
   |                            ------ immutable borrow occurs here
...
19 |                 for b_inner in bodies.iter_mut() {
   |                                ^^^^^^ mutable borrow occurs here
...
33 |             }
   |             - immutable borrow ends here

现在我被卡住了。在内循环结束后,Rust更新b_outer.v的方法是什么?

3 个答案:

答案 0 :(得分:4)

对于它的价值,我认为错误消息告诉您代码存在逻辑问题。如果在内循环的迭代之间更新向量,则这些更改将用于后续迭代。让我们看一个较小的例子,我们计算一个数组项及其邻居的窗口平均值:

[2, 0, 2, 0, 2] // input
[2/3, 4/3, 2/3, 4/3, 2/3] // expected output (out-of-bounds counts as 0)

[2/3, 0,      2, 0, 2] // input after round 1
[2/3, 8/9,    2, 0, 2] // input after round 2
[2/3, 8/9, 26/9, 0, 2] // input after round 3
// I got bored here

我建议将输出计算为临时数组,然后交换它们:

#[derive(Debug)]
struct Body {
    x: i16,
    y: i16,
    v: i16,
}

fn main() {
    let mut bodies = vec![
        Body { x: 10, y: 10, v: 0 }, 
        Body { x: 20, y: 30, v: 0 },
    ];

    for _ in 0..2 {
        let next_bodies = bodies
            .iter()
            .map(|b| {
                let next_v = bodies
                    .iter()
                    .fold(b.v, { |a, b_inner| a + b.x * b_inner.x });
                Body { v: next_v, ..*b }
            })
            .collect();
        bodies = next_bodies;
    }

    println!("{:?}", bodies);
}

输出:

[Body { x: 10, y: 10, v: 600 }, Body { x: 20, y: 30, v: 1200 }]

如果你真的关心内存性能,你可以创建总共两个向量,适当地调整它们的大小,然后在两者之间交替。但代码会更加丑陋。


正如Matthieu M.所说,你可以使用CellRefCell,这两者都授予你内在的可变性:

use std::cell::Cell;

#[derive(Debug, Copy, Clone)]
struct Body {
    x: i16,
    y: i16,
    v: i16,
}

fn main() {
    let bodies = vec![
        Cell::new(Body { x: 10, y: 10, v: 0 }),
        Cell::new(Body { x: 20, y: 30, v: 0 }),
    ];

    for i in 0..2 {
        println!("Turn {}", i);
        for b_outer_cell in &bodies {
            let mut b_outer = b_outer_cell.get();
            println!("{:?}", b_outer);

            let mut a = b_outer.v;
            for b_inner in &bodies {
                let b_inner = b_inner.get();
                a = a + b_outer.x * b_inner.x;
                println!("{:?}, a: {}", b_inner, a);
            }
            b_outer.v = a;
            b_outer_cell.set(b_outer);
        }
    }

    println!("{:?}", bodies);
}
[Cell { value: Body { x: 10, y: 10, v: 600 } }, Cell { value: Body { x: 20, y: 30, v: 1200 } }]

答案 1 :(得分:2)

我知道这个问题就像是2岁,但我很好奇。

这个C#程序产生原始的所需输出:

var bodies = new[] { new Body { X = 10, Y = 10, V = 0 },
                     new Body { X = 20, Y = 30, V = 0 } };

for (int i = 0; i < 2; i++)
{
    Console.WriteLine("Turn {0}", i);

    foreach (var bOuter in bodies)
    {
        Console.WriteLine("x:{0}, y:{1}, v:{2}", bOuter.X, bOuter.Y, bOuter.V);
        var a = bOuter.V;
        foreach (var bInner in bodies)
        {
            a = a + bOuter.X * bInner.X;
            Console.WriteLine("    x:{0}, y:{1}, v:{2}, a:{3}", bInner.X, bInner.Y, bInner.V, a);
        }
        bOuter.V = a;
    }
}

由于只更改v,我们可以将结构更改为以下内容:

struct Body {
    x: i16,
    y: i16,
    v: Cell<i16>,
}

现在我可以改变v,程序变为:

// keep it simple and loop only twice
for i in 0..2 {
    println!("Turn {}", i);
    for b_outer in bodies.iter() {

        let mut a = b_outer.v.get();

        println!("x:{}, y:{}, v:{}", b_outer.x, b_outer.y, a);
        for b_inner in bodies.iter() {

            a = a + (b_outer.x * b_inner.x);

            println!(
                "    x:{}, y:{}, v:{}, a:{}",
                b_inner.x,
                b_inner.y,
                b_inner.v.get(),
                a
            );
        }

        b_outer.v.set(a);
    }
}

它产生的输出与上面的C#程序相同。 “缺点”是,只要您想使用v,就需要使用get()into_inner()。可能还有其他缺点我不知道。

答案 2 :(得分:1)

我决定将结构拆分为一个用作内部循环(b_inner)中计算(输入)的基础的结构,以及收集结果(输出)的结构。内循环结束后,输入结构在外循环(b_outer)中更新,计算从下一个主体开始。

现在不太好,以至于我必须处理两个结构,而你却没有从声明中看到它们之间的关系。

#[derive(Debug)]
struct Body {
    x: i16,
    y: i16,
}

struct Velocity {
    vx: i16,
}

fn main() {
    let mut bodies = Vec::<Body>::new();
    let mut velocities = Vec::<Velocity>::new();

    bodies.push(Body { x: 10, y: 10 });
    bodies.push(Body { x: 20, y: 30 });
    velocities.push(Velocity { vx: 0 });
    velocities.push(Velocity { vx: 0 });

    // keep it simple and loop only twice
    for i in 0..2 {
        println!("Turn {}", i);
        for (i, b_outer) in bodies.iter().enumerate() {
            println!("x:{}, y:{}, v:{}", b_outer.x, b_outer.y, velocities[i].vx);
            let v = velocities.get_mut(i).unwrap();
            let mut a = v.vx;
            for b_inner in bodies.iter() {
                // for simplicity I ignore here to continue in case b_outer == b_inner
                // just do some calculation
                a = a + b_outer.x * b_inner.x;
                println!("    x:{}, y:{}, v:{}, a:{}", b_inner.x, b_inner.y, v.vx, a);
            }
            v.vx = a;
        }
    }

    println!("{:?}", bodies);
}

输出:

[Body { x: 10, y: 10 }, Body { x: 20, y: 30 }]