【问题标题】:What's the Rust way to modify a structure within nested loops?在嵌套循环中修改结构的 Rust 方法是什么?
【发布时间】:2020-01-23 01:59:07
【问题描述】:

Given 是一组以某种方式相互作用的物体。作为一个新手,我用其他语言处理它:

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

现在我被困住了。内循环完成后更新 b_outer.v 的 Rust 方法是什么?

【问题讨论】:

  • 在您的情况下,我认为您可以尝试Vec&lt;Cell&lt;Body&gt;&gt;,因为Cell 具有“内在可变性”。
  • 注意:我在这里忽略了“显而易见”的答案,创建了 bodies 的克隆,并在迭代 bodies 时修改克隆。它的明显缺点是需要两倍的内存。
  • @MatthieuM。看我的回答 - 我认为在迭代期间就地修改数组对于这个问题是不正确的。
  • @Shepmaster:啊,我没想过挑战逻辑 :)

标签: rust


【解决方案1】:

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

[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. 所说,您可以使用 Cell 或 RefCell,它们都赋予您内在的可变性:

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 _ in 0..2 {
        for b_outer_cell in &bodies {
            let mut b_outer = b_outer_cell.get();

            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;
            }
            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 } }]

【讨论】:

  • 关于逻辑问题,如果在内部循环(b_inner)期间更改 b.v,我会同意。但事实并非如此。内部循环(b_inner)的 v 在内部循环期间不会改变。 b.v 在内循环完成后更新。然后 b_outer 前进到下一个主体。
  • 同时我想出了另一个解决方案(虽然我不知道这是否更优雅):我拆分结构,一个是计算(输入)的基础,另一个是收集结果(输出)。计算完成后,输入结构用输出结构的结果更新,下一轮计算开始。
  • 我想知道使用 Cell 的解决方案会是什么样子。我正在尝试使用 Cells 重写他的第一个代码,但我一直遇到原始问题。
  • @Shepmaster 这很快,谢谢!我想你必须派生Copy, Clone 才能使用get。我也在写一个答案来分享我的发现,虽然我只是在学习 Rust。
【解决方案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()。可能还有其他我不知道的缺点。

【讨论】:

  • 对我来说似乎很合理。这样做的好处是非常明确地知道在迭代期间可以更改的内容。
【解决方案3】:

我决定将结构拆分为一个用作内部循环 (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 }]

【讨论】:

  • 只是想指出,这些变化可能并不反映最初的意图。当然,两种情况的最终结果是一样的,只是标准输出略有不同。
猜你喜欢
  • 2010-10-14
  • 1970-01-01
  • 2010-09-16
  • 2020-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-20
  • 1970-01-01
相关资源
最近更新 更多