Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the Rust way to modify a structure within nested loops?

Tags:

rust

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?

like image 977
Chris Avatar asked Mar 16 '23 13:03

Chris


1 Answers

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 vector 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 _ 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 } }]
like image 166
Shepmaster Avatar answered Mar 24 '23 05:03

Shepmaster