0

I'm trying to insert a value into a HashMap based on another value in the same HashMap, like so:

use std::collections::HashMap;

fn main() {
    let mut some_map = HashMap::new();
    some_map.insert("a", 1);

    let some_val = some_map.get("a").unwrap();

    if *some_val != 2 {
        some_map.insert("b", *some_val);
    }
}

which gives this warning:

warning: cannot borrow `some_map` as mutable because it is also borrowed as immutable
  --> src/main.rs:10:9
   |
7  |     let some_val = some_map.get("a").unwrap();
   |                    -------- immutable borrow occurs here
...
10 |         some_map.insert("b", *some_val);
   |         ^^^^^^^^             --------- immutable borrow later used here
   |         |
   |         mutable borrow occurs here
   |
   = note: `#[warn(mutable_borrow_reservation_conflict)]` on by default
   = warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future
   = note: for more information, see issue #59159 <https://github.com/rust-lang/rust/issues/59159>

If I were instead trying to update an existing value, I could use interior mutation and RefCell, as described here.

If I were trying to insert or update a value based on itself, I could use the entry API, as described here.

I could work around the issue with cloning, but I would prefer to avoid that since the retrieved value in my actual code is somewhat complex. Will this require unsafe code?

  • You are cloning `some_val` in the last line anyway, so you may just as well do that earlier – shouldn't make much of a difference. – Sven Marnach May 07 '20 at 20:32

1 Answers1

0

EDIT Since previous answer is simply false and doesn't answer the question at all, there's code which doesn't show any warning (playground)

Now it's a hashmap with Rc<_> values, and val_rc contains only a reference counter on actual data (number 1 in this case). Since it's just a counter, there's no cost of cloning it. Note though, that there's only one copy of a number exists, so if you modify a value of some_map["a"], then some_map["b"] is modified aswell, since they refer to a single piece of memory. Also note, that 1 lives on stack, so you better consider turn it into Rc<Box<_>> if you plan to add many heavy objects.

use std::collections::HashMap;
use std::rc::Rc;

fn main() {
    let mut some_map = HashMap::new();
    some_map.insert("a", Rc::new(1));

    let val_rc = Rc::clone(some_map.get("a").unwrap());
    if *val_rc != 2 {
        some_map.insert("b", val_rc);
    }

}

Previous version of answer

Hard to tell what exactly you're looking for, but in this particular case, if you only need to check the value, then destroy the borrowed value, before you update the hashmap. A dirty and ugly code would be like this:

fn main() {
    let mut some_map = HashMap::new();
    some_map.insert("a", 1);

    let is_ok = false;

    {
        let some_val = some_map.get("a").unwrap();
        is_ok = *some_val != 2;
    }

    if is_ok {
        some_map.insert("b", *some_val);
    }
}
Alexey S. Larionov
  • 6,555
  • 1
  • 18
  • 37
  • ...why didn't I think of this? Man, I'm dumb. Suppose I needed to reuse the value in each iteration of a loop. Would I have to get and destroy the value in each iteration, or is there a way I could preserve the value across the whole loop? – VeryCasual May 07 '20 at 19:52
  • @VeryCasual you get just a reference to the object, so making and destroying it is not costly, except the time for searching the key in the hashmap. In any language it's a bad practice to have active references while you change your collection. Hash map has amortised complexity of `O(1)` for key search, so unless your loop is super-duper hot and it executes a million of iterations per second always (like sort of graphics renderer or a web server), you don't need to worry about destroying that reference and searching it again, the performance impact isn't that bad. – Alexey S. Larionov May 07 '20 at 20:06
  • Ah, of course. I also should have thought of that. Rust seems to be frying my brain and making me forget simple things. Marking this answer as accepted. – VeryCasual May 07 '20 at 20:29
  • I don't get it. Won't `some_val` be out of scope in the last line? – Sven Marnach May 07 '20 at 20:30
  • @SvenMarnach woops, indeed. Then a reference counting is probably required, please OP unaccept the answer if you can :) I didn't see that you try to duplicate the value without cloning it – Alexey S. Larionov May 07 '20 at 20:40
  • @SvenMarnach Unaccepting for the moment. I think I should be able to re-accept the answer if it's updated to look something like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f873c69902c0c6be1587170822fa6102) (Rust playground). – VeryCasual May 07 '20 at 20:59
  • @VeryCasual I edited the answer with somewhat a solution, hope my brain isn't yet fried at 2 am. – Alexey S. Larionov May 07 '20 at 21:55
  • In my case, I do want to clone the value, but only *after* I've verified it meets the necessary conditions, in order to avoid unnecessary cloning. Sorry for unclear phrasing. Still, this meets the requirements of my question as written, so I'm going to accept it unless someone gets mad at me. Also, brains are supposed to be fried at 2am :). The sun's still up where I am, so I've got no excuse. – VeryCasual May 07 '20 at 22:17