Beginner level: I don't understand the following warning message:

Hi Rusticians,

I'm trying to learn Rust and I don't understand the following warning message:
Could you explain it to me?

use std::collections::HashMap;

fn main() {
    
    let mut m: HashMap<String, i32> = HashMap::new();

    for word in vec!["one", "one", "two"] {
        match m.get(word) {
            Some(c) => {
                m.insert(String::from(word), c + 1);
            }
            None => {
                m.insert(String::from(word), 1);
            }
        }
    }
    println!("{:?}", m);
}
warning: cannot borrow `m` as mutable because it is also borrowed as immutable
  --> src\main.rs:10:17
   |
8  |         match m.get(word) {
   |               - immutable borrow occurs here
9  |             Some(c) => {
10 |                 m.insert(String::from(word), c + 1);
   |                 ^ mutable borrow occurs here - immutable borrow later used 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>

warning: 1 warning emitted

    Finished dev [unoptimized + debuginfo] target(s) in 0.82s
m.insert(String::from(word), c + 1);

This is the same as:

HashMap::insert(&mut m, String::from(word), c + 1)

which however doesn't compile because &mut m is evaluated before c + 1, and that mutable borrow of m invalidates the previous shared borrow that c is holding. However, the borrow checker used to allow this code so for backwards compatibility this can't be an hard error for now, but it may become in the future. You should avoid this pattern if you don't want your code to suddently break with a new version of the compiler.

The simpliest fix is to calculate c + 1 before the insert save it in a temporary variable and then use that in the insert.

Fixed code
use std::collections::HashMap;

fn main() {
    
    let mut m: HashMap<String, i32> = HashMap::new();

    for word in vec!["one", "one", "two"] {
        match m.get(word) {
            Some(c) => {
                let new_c = c + 1;
                m.insert(String::from(word), new_c);
            }
            None => {
                m.insert(String::from(word), 1);
            }
        }
    }
    println!("{:?}", m);
}
1 Like

Thank you very much!

FYI, the Rust hashmap (and btreemap) has a very handy "Entry API" very useful in cases like this :

use std::collections::HashMap;

fn main() {
    let mut m: HashMap<String, i32> = HashMap::new();
    for word in vec!["one", "one", "two"] {
        *m.entry(word.to_string()).or_default() += 1;
    }
    println!("{:?}", m);
}

The doc for the entry method std::collections::hash_map::HashMap - Rust (the example in the doc is litterally that same counter) and for the Entry type std::collections::hash_map::Entry - Rust. This also avoids the "double key lookup" problem.

1 Like