3

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?

makapuf
  • 1,370
  • 1
  • 13
  • 23
  • 1
    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). – trent Dec 15 '19 at 20:08
  • 2
    very similar to https://stackoverflow.com/q/45724517/5986907 – joel Dec 16 '19 at 10:36
  • Does edwardw's answer work for you or are you still waiting for something different? – trent Dec 17 '19 at 13:41
  • 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 back – makapuf Dec 17 '19 at 17:29
  • 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 Dec 17 '19 at 21:22

2 Answers2

5

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"
}
joel
  • 6,359
  • 2
  • 30
  • 55
  • interesting, but the goal of my processing step is that in some cases, I need to _add_ elements to to_process (say like with a list of possible options). – makapuf Dec 15 '19 at 21:05
  • @makapuf you should update the question and title to reflect that - the comment in your code snippet is off screen and not obvious – joel Dec 16 '19 at 10:43
4

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
    }
}
edwardw
  • 12,652
  • 3
  • 40
  • 51