Cannot borrow `map` as mutable because it is also borrowed as immutable?

Hello there,

I am not completely new to Rust but I guess still learning :smiley:

I have the following function and it works fine, however our lovely friend aka the borrow-checker is not happy :confused:
Can someone please tell me how I can get the same result without the warning?

use std::collections::HashMap;

/// Counts occurences of every char in `text`.
///
/// ```
/// assert_eq!(count_chars(""), HashMap::new());
/// assert_eq!(count_chars("a"), HashMap::from([('a', 1)]));
/// assert_eq!(
///     count_chars("abacab"),
///     HashMap::from([('a', 3), ('b', 2), ('c', 1)])
/// );
/// ```
fn count_chars(text: &str) -> HashMap<char, i32> {
    let mut map = HashMap::new();

    for c in text.chars() {
        let counter = map.get(&c).unwrap_or(&0);
        map.insert(c, counter + 1);
    }

    map
}
warning: cannot borrow `map` as mutable because it is also borrowed as immutable
   --> ***.rs:117:9
    |
116 |         let counter = map.get(&c).unwrap_or(&0);
    |                       --- immutable borrow occurs here
117 |         map.insert(c, counter + 1);
    |         ^^^           ------- 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>

you're storing a reference in counter that's still pointing into the map when insert is called. The only actual problem here is a bit theoretical though: the self argument of the method call to insert, i. e. a mutable reference to map is β€œevaluated” before the arguments are evaluated, so your code tries to first create the new mutable reference, and only afterwards calculate counter + 1 which is still using the previously created immutable reference into the map. If you put some let new_counter = counter + 1 definition first and used new_counter for the insertion, things should work out fine.

You could also change the code such that counter is no reference at all, e. g. map.get(&c).copied().unwrap_or(0). (This is probably the nicer fix.)

The reason why this is only a warning is because there's special rules with the effect of - in some cases - delaying the creation of the mutable reference in the method call until after the arguments are evaluated, but apparently your case is one that was not intended to be supported. See the issue the warning points to for more information.

1 Like

Ah :bulb: thanks a lot!

Especially for map.get(&c).copied().unwrap_or(0) - most helpful!

you could use the method or_insert like that:

fn count_chars(text: &str) -> HashMap<char, i32> {
    let mut result = HashMap::new();
    for c in text.chars() {
        *result.entry(c).or_insert(0) += 1;
    }
    result
}
4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.