Search code examples
rustborrow-checker

Modify HashMap with values from other HashMap whose keys are borrowed from the first one


Suppose I have two HashMaps, m1 and m2 with the same set of non-Copy keys, except m2’s keys are borrowed from m1 (say, m1 has String keys and m2 has &str keys borrowed from m1). Is it possible to iterate through m1 and update its values using the corresponding values from m2? The compiler believes that since m2 borrows (immutably) from m1, borrowing m1 mutably (say, through iter_mut() or get_mut()) should not be allowed.

Here is an MVE, demonstrating two attempts at iteration:

use std::collections::HashMap;

fn main() {
    let mut m1 = HashMap::<_, _>::from_iter([("a".to_owned(), 1), ("b".to_owned(), 2)]);
    let m2 = m1
        .iter()
        .map(|(k, v)| (k.as_str(), v))
        .collect::<HashMap<_, _>>();

    for (k, v) in m1.iter_mut() {
        *v += m2[k.as_str()];
    }

    for (&k, &v) in m2.iter() {
        *m1.get_mut(k).unwrap() += v;
    }
}
error[E0502]: cannot borrow `m1` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:16
   |
5  |     let m2 = m1
   |              -- immutable borrow occurs here
...
10 |     for (k, v) in m1.iter_mut() {
   |                   ^^^^^^^^^^^^^ mutable borrow occurs here
11 |         *v += m2[k.as_str()];
   |               -- immutable borrow later used here

error[E0502]: cannot borrow `m1` as mutable because it is also borrowed as immutable
  --> src/main.rs:15:4
   |
5  |     let m2 = m1
   |              -- immutable borrow occurs here
...
14 |     for (&k, &v) in m2.iter() {
   |                     --------- immutable borrow later used here
15 |         *m1.get_mut(k).unwrap() += v;
   |          ^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.

I believe that what I'm trying to do is sound because modifying existing values of a HashMap won't cause any of its keys to move, and so m2’s references to m1’s keys will remain valid. But it doesn't seem possible to express this to the compiler?

I've considered using Rc<String> keys or just usize and storing the Strings in a Vec, but I'm wondering if there's not a simpler way.


Solution

  • Generally speaking, no, the compiler cannot allow you &mut access to m1 because you could e.g. invoke .clear() and destroy the values that are being borrowed by m2.

    Using Rc for the keys seems like a sensible solution to me.

    Another option to consider would be using an interior mutability type (Cell or RefCell) for m1's values, which would allow you to mutate them without &mut access to m1. Since the values are numbers, Cell is probably a good fit since it has no reference-counting overhead like RefCell does.