Vector of struct : iterate through struct attribute to modify other struct in same vector ==> E0502

Hey Rustaceans !

I have a small problem with vectors at the moment, nothing extraordinary but i'm struggling to code what I want.

I have a vector of a a custom struct :Node.

struct Node{
    is_gateway : bool,
    next_node :  Vec<usize>,
    nb_link_to_gateway : i32,
}

As you can see nothing too fancy.

Here is the problem : I would like to iterate through the next_node attribute of a gateway, and add 1 to the attribute nb_link_to_gateway to all nodes listed.

I wrote this code :

for node in &nodes[ei].next_node{
    nodes[*node].link_to_gateway += 1;
 }

*note : nodes[ei] is pointing to a gateway.

Which gives me this error:

error[E0502]: cannot borrow `nodes` as mutable because it is also borrowed as immutable
  --> /tmp/Answer.rs:64:17
   |
63 |             for node in &nodes[ei].next_node{
   |                         --------------------
   |                         ||
   |                         |immutable borrow occurs here
   |                         immutable borrow later used here
64 |                 nodes[*node].link_to_gateway += 1;
   |                 ^^^^^ mutable borrow occurs here

I understand the error, but I don't know how to remove it. I tried different solution, all gives the same result.

Any ideas how to do it ?

So the fundamental problem here is that the compiler can't prove that node will never be equal to ei and thus you are not reading and writing the same value at the same time.

As the simplest solution, if this piece of code isn't yet a performance bottleneck, you could just clone the vector:

for node in nodes[ei].next_node.clone() {
    nodes[node].link_to_gateway += 1;
}

If you want to avoid cloning at all costs, you could split the vector instead and juggle indices around:

let (before, tmp) = nodes.split_at_mut(ei);
let (gateways, after) = tmp.split_at_mut(1);
let gateway = &gateways[0];

for &node in &gateway.next_node {
    if node < ei {
        before[node].link_to_gateway += 1;
    } else {
        after[node - ei - 1].link_to_gateway += 1;
    }
}

I'm not at all sure though that the latter performs better than the former, because it involves array bounds checking and a conditional on every iteration. My guess would be that when there are typically few elements in the next_node vectors, then the memory allocation overhead of cloning is slower than a couple conditionals, but if there are a lot of node indices in next_node, then the resulting array bounds checks and conditionals will dominate the time of a one-off cloning.

1 Like

You could take next_node out the time of the iteration and put it back afterwards.

let mut next_node = Vec::new();
std::mem::swap(&mut next_node, &mut nodes[ei].next_node);

for &node in &next_node {
    nodes[node].nb_link_to_gateway += 1;
}

std::mem::swap(&mut next_node, &mut nodes[ei].next_node);
2 Likes

I see, thank's for your solutions. ^^

The code is not that critical and is only used once.

I suppose that if I want to change

next_node :  Vec<usize>

to

next_node :  Vec<&'static Node>

the same solutions will work. But I don't know if, in this situation, it's a better data structure. I tend to think not.

Also, isn't there a system to tell the compiler to dot less check on the code ? I think about the 'unsafe' feature. It could bypass the problem ? I tried using it, but i don't know if I used it correctly :confused: or if it does not bypass the checks associated to the error E0502.

If it's not performance-critical, don't even think about unsafe.

All right :sweat_smile: . I will stay in the safe zone.

I like that solution. It's almost as simple as cloning and doesn't involve lots of conditionals. However, I think it can be simplified a bit more, working with values rather than references (@thenewbby):

let next_node = mem::replace(&mut nodes[ei].next_node, Vec::new());

for &node in &next_node {
    nodes[node].nb_link_to_gateway += 1;
}

mem::replace(&mut nodes[ei].next_node, next_node);
1 Like