167

Here is what I am trying to do:

use std::collections::HashMap;

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

    my_map["a"] += 10;
    // I expect my_map becomes {"b": 3, "a": 11}
}

But this raises an error:

Rust 2015

error[E0594]: cannot assign to immutable indexed content
 --> src/main.rs:8:5
  |
8 |     my_map["a"] += 10;
  |     ^^^^^^^^^^^^^^^^^ cannot borrow as mutable
  |
  = help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `std::collections::HashMap<&str, i32>`

Rust 2018

error[E0594]: cannot assign to data in a `&` reference
 --> src/main.rs:8:5
  |
8 |     my_map["a"] += 10;
  |     ^^^^^^^^^^^^^^^^^ cannot assign

I don't really understand what that means, since I made the HashMap mutable. When I try to update an element in a vector, I get the expected result:

let mut my_vec = vec![1, 2, 3];

my_vec[0] += 10;
println! {"{:?}", my_vec};
// [11, 2, 3]

What is different about HashMap that I am getting the above error? Is there a way to update a value?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Akavall
  • 82,592
  • 51
  • 207
  • 251

4 Answers4

209

Indexing immutably and indexing mutably are provided by two different traits: Index and IndexMut, respectively.

Currently, HashMap does not implement IndexMut, while Vec does.

The commit that removed HashMap's IndexMut implementation states:

This commit removes the IndexMut impls on HashMap and BTreeMap, in order to future-proof the API against the eventual inclusion of an IndexSet trait.

It's my understanding that a hypothetical IndexSet trait would allow you to assign brand-new values to a HashMap, and not just read or mutate existing entries:

let mut my_map = HashMap::new();
my_map["key"] = "value";

For now, you can use get_mut:

*my_map.get_mut("a").unwrap() += 10;

Or the entry API:

*my_map.entry("a").or_insert(42) += 10;
Coder
  • 1,415
  • 2
  • 23
  • 49
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 6
    Is IndexMut expected to be implemented in the future? – Luke Dupin Jan 24 '18 at 17:22
  • 3
    @LukeDupin doubtful. In its place the hypothetical `IndexSet` trait would be implemented. – Shepmaster Jan 25 '18 at 04:02
  • 3
    `*my_map.entry("a").or_insert(42) += 10;` Whad does that * do? Dereferencing for what part? – Eftekhari Jun 30 '19 at 23:07
  • 3
    @Eftekhari with added parenthesis: `(*(my_map.entry("a").or_insert(42))) += 10;` – Shepmaster Jul 02 '19 at 00:01
  • hello, How can I split the last line, imagine Im working with stucts and I want to change 2 values, I will not call get twice so... ```(*example_map.get_mut(&1).unwrap()).field1+=55;``` some one can explain this to me, Im dealing with this and I dont know what to write, if let. let mut, let &mut ... Thanks! – Germán Faller Mar 30 '20 at 04:36
  • 1
    @GermanFaller `let tmp = my_map.entry("a").or_insert(some_default); tmp.a += 1; tmp.b += 2`. – Shepmaster Mar 30 '20 at 13:56
  • Also, if I understand correctly, if you just want to update the value of a certain key, you can simply insert the new pair `(key, map)` into a map and get what you want. – Artyom Gevorgyan Sep 14 '20 at 15:48
  • @ArtemHevorhian perhaps, but that's not how I'd usually interpret "update". To me, update means "based on the previous value", while inserting again would be closer to replacing. Additionally for Rust, you may wish to mutate the value (e.g. add something to a `Vec`) instead of creating a whole new value. – Shepmaster Sep 14 '20 at 16:35
35

Considering:

let mut m = std::collections::HashMap::new();
m.insert("a", 1);
m.insert("b", 3);
let k = "c";

If the key already exists:

    m.insert(k, 10 + m[k] );

If the key not exists:

  1. You may update a value of the key:
    m.insert(k, 10 + if m.contains_key(k) { m[k] } else { 0 });
  1. Or first insert a key only if it doesn't already exist:
    m.entry(k).or_insert(0);
    m.insert(k, 200 + m[k]);
  1. Or update a key, guarding against the key possibly not being set:
    *m.entry(k).or_insert(0) += 3000;

Finally print the value:

    println!("{}", m[k]); // 3210

See:
https://doc.rust-lang.org/std/collections/struct.HashMap.html

wasmup
  • 14,541
  • 6
  • 42
  • 58
6

I will share my own Answer because I had this issue but I was working with Structs so, that way in my case was a little bit tricky

use std::collections::HashMap;

#[derive(Debug)]
struct ExampleStruct {
    pub field1: usize,
    pub field2: f64,
}

fn main() {
    let mut example_map = HashMap::new();
    &example_map.insert(1usize, ExampleStruct { field1: 50, field2: 184.0});
    &example_map.insert(6usize, ExampleStruct { field1: 60, field2: 486.0});

    //First Try
    (*example_map.get_mut(&1).unwrap()).field1 += 55; //50+55=105
    (*example_map.get_mut(&6).unwrap()).field1 -= 25; //60-25=35

    //Spliting lines
    let op_elem = example_map.get_mut(&6);
    let elem = op_elem.unwrap();
    (*elem).field2 = 200.0;

    let op_ok_elem = example_map.get_mut(&1);
    let elem = op_ok_elem.unwrap_or_else(|| panic!("This msg should not appear"));
    (*elem).field2 = 777.0;

    println!("Map at this point: {:?}", example_map);
    let op_err_elem = example_map.get_mut(&8);
    let _elem = op_err_elem.unwrap_or_else(|| panic!("Be careful, check you key"));

    println!("{:?}", example_map);
}

You can play with this on Rust Playground

Germán Faller
  • 546
  • 7
  • 15
4

You can do this with .and_modify

let mut my_map = HashMap::new();
my_map.insert("a", 1);
my_map.entry("a").and_modify(|k| *k += 10);
assert_eq!(my_map[&"a"], 11);
schwartz
  • 189
  • 8