How to do a loop inside another loop when need to use same mutable

Hello. I have the following piece of code:

use std::collections::HashMap;

struct Item {
    name: String,
    level: u8,
    children_by_names: Vec<String>,
    children_by_level: HashMap<u8, String>
}

struct App {
    items_by_name: HashMap<String, Item>
}

impl App {
    pub fn process(&mut self) {
        for (_, item) in &self.items_by_name {
            for name_of_child in &item.children_by_names {
                let child = self.items_by_name.get_mut(name_of_child).unwrap();
                child.children_by_level.insert(item.level, item.name.clone());
            }
        }
    }
}

fn main() {
    println!("Started");
}

(Playground)

And of course I receive the following error:

error[E0502]: cannot borrow `self.items_by_name` as mutable because it is also borrowed as immutable
  --> src/main.rs:18:29
   |
16 |         for (_, item) in &self.items_by_name {
   |                          -------------------
   |                          |
   |                          immutable borrow occurs here
   |                          immutable borrow later used here
17 |             for name_of_child in &item.children_by_names {
18 |                 let child = self.items_by_name.get_mut(name_of_child).unwrap();
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` due to previous error

I understand why, but don't know how to solve it.
What is the best way to do this?

Maybe by using an IndexMap in indexmap::map - Rust for items_by_name, you could do the trick usually applicable only to Vec of iterating by-index instead of with an iterator.

It's not pretty, but it might be a first step? (Assuming I didn't change the logic unintentionally) Rust Playground

1 Like

Thank you. Looks pretty good (as the logic was not changed).

There is no there is no other way using the HashMap and the Vec? (not that the solution offered is not good, just want to know if there are other ways to do this)

An alternative is to refactor the inner loop to another function

You can pre-split your borrows. Note: completely untested.

Or variations on this theme like

// pending: `&item.name` mapped by `(&name_of_child, item.level)`
for name_of_child in &item.children_by_names {
    pending.insert((name_of_child, item.level), &item.name);
}
for name_of_child in &item.children_by_names {
    if let Some(cbl) = levels.get_mut(name_of_child) {
        cbl.insert(item.level, item.name.clone();
    } else {
        pending.insert((name_of_child, item.level), &item.name);
    }
}

Depending on the shape of your data.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.