3
votes

I would like to process the values from a HashMap one by one, while maybe removing some of them.

For example, I would like to do an equivalent of:

use std::collections::HashMap;

fn example() {
    let mut to_process = HashMap::new();
    to_process.insert(1, true);

    loop {
        // get an arbitrary element
        let ans = to_process.iter().next().clone(); // get an item from the hash
        match ans {
            Some((k, v)) => {
                if condition(&k,&v) {
                    to_process.remove(&k);
                }
            }
            None => break, // work finished
        }
    }
}

But this fails to compile:

error[E0502]: cannot borrow `to_process` as mutable because it is also borrowed as immutable
  --> src/lib.rs:12:17
   |
9  |         let ans = to_process.iter().next().clone();
   |                   ---------- immutable borrow occurs here
...
12 |                 to_process.remove(&k);
   |                 ^^^^^^^^^^^------^^^^
   |                 |          |
   |                 |          immutable borrow later used by call
   |                 mutable borrow occurs here

I know I really would need https://github.com/rust-lang/rust/issues/27804 (which is for HashSet but for HashMap would be the same) and I cannot implement the provided solutions without having a non-mut and mutable reference still or using unsafe.

Is there a simple way I am missing?

2
I notice that the .clone() does nothing useful -- because next() returns Option<(&K, &V)>, it just copies the references. If you wanted to get an Option<(K, V)> by cloning you would need something like .map(|(k, v)| (k.clone(), v.clone())) (but it seems unlikely you would need to).trentcl
Does edwardw's answer work for you or are you still waiting for something different?trentcl
not exactly, I wasn't very clear : I updated the title and (bad) example to better reflect my intentions : consume elements from the map, and maybe add backmakapuf
I guess this was a translation error from my side. Since the answer was interesting and did correspond the original, different question, I will revert my edit, choose an answer and then post a new question.makapuf

2 Answers

5
votes

Note If you need to alter keys or add kvps to the HashMap during processing, see @edwardw's answer. Otherwise ...

Use HashMap::retain. You can change your process function to return a bool indicating whether to keep that key value pair. For example

let mut to_process: HashMap<u32, String> = HashMap::new();
to_process.insert(1, "ok".to_string());
to_process.insert(2, "bad".to_string());

to_process.retain(process);
    
fn process(k: &u32, v: &mut String) -> bool {
    // do stuff with k and v
    v == "ok"
}
4
votes

This looks like an awfully good fit for Iterator::filter_map:

The closure must return an Option<T>. filter_map creates an iterator which calls this closure on each element. If the closure returns Some(element), then that element is returned. If the closure returns None, it will try again, and call the closure on the next element, seeing if it will return Some.

The following process_and_maybe_add is very simple, but you get the idea:

use std::collections::HashMap;

fn main() {
    let mut data = HashMap::new();
    data.insert(1, "a");
    data.insert(2, "b");
    data.insert(3, "c");

    let processed = data
        .into_iter()
        .filter_map(process_and_maybe_add)
        .collect::<HashMap<_, _>>();
    dbg!(processed);
}

fn process_and_maybe_add((k, v): (u32, &str)) -> Option<(u32, String)> {
    if k % 2 != 0 {
        Some((k + 100, v.to_owned() + v))
    } else {
        None
    }
}