What's the Rust way to modify a structure with

2019-02-14 17:04发布

Given is an array of bodies that interact in some way with each other. As a newbie I approached it as I would do it in some other language:

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

Updating of b_outer.v after the inner loop has finished fails:

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

Making b_outer mutable:

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

doesn't work either:

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

Now I'm stuck. What's the Rust approach to update b_outer.v after the inner loop has finished?

标签: rust
3条回答
Evening l夕情丶
2楼-- · 2019-02-14 17:11

For what it's worth, I think the error message is telling you that your code has a logic problem. If you update the vector between iterations of the inner loop, then those changes will be used for subsequent iterations. Let's look at a smaller example where we compute the windowed-average of an array item and its neighbors:

[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

I'd suggest computing the output into a temporary array and then swap them:

#[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);
}

Output:

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

If you really concerned about memory performance, you could create a total of two vectors, size them appropriately, then alternate between the two. The code would be uglier though.


As Matthieu M. said, you could use Cell or RefCell, which both grant you inner mutability:

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 } }]
查看更多
手持菜刀,她持情操
3楼-- · 2019-02-14 17:35

I know the question is like 2 years old, but I got curious about it.

This C# program produces the original desired output:

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

Since only v is ever changed, we could change the struct to something like this:

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

Now I'm able to mutate v, and the program becomes:

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

It produces the same output as the C# program above. The "downside" is that whenever you want to work with v, you need use get() or into_inner(). There may be other downsides I'm not aware of.

查看更多
SAY GOODBYE
4楼-- · 2019-02-14 17:36

I decided to split the structure in one that is used as a base for the calculation (input) in the inner loop (b_inner) and one that gathers the results (output). After the inner loop finished, the input structure is updated in the outer loop (b_outer) and calculation starts with the next body.

What's now not so nice that I have to deal with two structures and you don't see their relation from the declaration.

#[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);
}

Output:

[Body { x: 10, y: 10 }, Body { x: 20, y: 30 }]
查看更多
登录 后发表回答