Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I simultaneously iterate over a Rust HashMap and modify some of its values?

I'm trying Advent of Code in Rust this year, as a way of learning the language. I've parsed the input (from day 7) into the following structure:

struct Process {
    name: String,
    weight: u32,
    children: Vec<String>,
    parent: Option<String>
}

These are stored in a HashMap<String, Process>. Now I want to iterate over the values in the map and update the parent values, based on what I find in the parent's "children" vector.

What doesn't work is

for p in self.processes.values() {
    for child_name in p.children {
        let mut child = self.processes.get_mut(child_name).expect("Child not found.");
        child.parent = p.name;
    }
}

I can't have both a mutable reference to the HashMap (self.processes) and a non-mutable reference, or two mutable references.

So, what is the most idiomatic way to accomplish this in Rust? The two options I can see are:

  1. Copy the parent/child relationships into a new temporary data structure in one pass, and then update the Process structs in a second pass, after the immutable reference is out of scope.
  2. Change my data structure to put "parent" in its own HashMap.

Is there a third option?

like image 886
Mark Gritter Avatar asked Dec 10 '17 08:12

Mark Gritter


People also ask

Can we iterate over elements stored in Java HashMap?

In Java HashMap, we can iterate through its keys, values, and key/value mappings.

How do Hashmaps work rust?

Hashmap in rust is a structure which comprises of the look-up table and data in it in form of key and value pair which will be used for storing and retrieving data continuously. Hashmap needs to be explicitly imported from the rust inbuilt library collection before that can be used within the program.


1 Answers

Yes, you can grant internal mutability to the HashMap's values using RefCell:

struct ProcessTree {
    processes: HashMap<String, RefCell<Process>>,  // change #1
}

impl ProcessTree {
    fn update_parents(&self) {
        for p in self.processes.values() {
            let p = p.borrow();                    // change #2
            for child_name in &p.children {
                let mut child = self.processes
                    .get(child_name)               // change #3
                    .expect("Child not found.")
                    .borrow_mut();                 // change #4
                child.parent = Some(p.name.clone());
            }
        }
    }
}

borrow_mut will panic at runtime if the child is already borrowed with borrow. This happens if a process is its own parent (which should presumably never happen, but in a more robust program you'd want to give a meaningful error message instead of just panicking).

I invented some names and made a few small changes (besides the ones specifically indicated) to make this code compile. Notably, p.name.clone() makes a full copy of p.name. This is necessary because both name and parent are owned Strings.

like image 129
trent Avatar answered Oct 11 '22 00:10

trent