0

I'm trying to build up a hashmap that counts character frequencies in a string. My approach is to fold over the characters in the string, incrementing the count for the current character at each iteration. Unfortunately, rust is telling me that I'm doing something wrong with borrows.

No variation of using clone() or breaking out the hm.get(&c) onto a separate line has any effect.

I don't see how to avoid this error about two-phase borrows. (relevant rust-lang github issue)

use std::collections::HashMap;

fn main() {
    let some_str = "some string";

    let hm = some_str.chars().fold(HashMap::new(), |mut hm, c| {
        match hm.get(&c) {
            None => hm.insert(c, 1),
            Some(i) => hm.insert(c, i + 1),
        };
        hm
    });

    for (key, val) in hm.iter() {
        println!("{}: {}", key, val);
    }
}

which gives this error

warning: cannot borrow `hm` as mutable because it is also borrowed as immutable
 --> dank.rs:9:24
  |
7 |         match hm.get(&c) {
  |               -- immutable borrow occurs here
8 |             None => hm.insert(c, 1),
9 |             Some(i) => hm.insert(c, i+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>
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Captain_Obvious
  • 520
  • 5
  • 15
  • I have sevaral questions, why did you include lines number and where is line number 11 ? – Stargateur Mar 12 '20 at 23:30
  • 1
    Does this answer your question? [How to lookup from and insert into a HashMap efficiently?](https://stackoverflow.com/questions/28512394/how-to-lookup-from-and-insert-into-a-hashmap-efficiently) – Stargateur Mar 12 '20 at 23:35
  • [apply duplicate answers](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=a43c3c5fb64497148f5e3ff5e1723024) – Stargateur Mar 12 '20 at 23:38
  • @Stargateur I will consider your answer correct, but I don't understand why my code violates the borrow-checker. That is my question. – Captain_Obvious Mar 12 '20 at 23:41
  • Normally your code should not compile, [here](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=13c67732700d9591410358aa095bf3ff) you can look how you could fix your code. Also, if you look at [`example()`](https://stackoverflow.com/a/50253558/7076153) in the shep answers's, you will see that you could also have use `get_mut()` that doesn't expose this problem (and is better). – Stargateur Mar 12 '20 at 23:50

2 Answers2

3

credit to @Stargateur for the answer to my question

The issue was that when we insert i + 1 back into the hashmap, the variable i is actually borrowed from the hashmap. If we copy i first, our immutable borrow of hm ends where we copy i, which is before we use a mutable reference to hm for the insert().

use std::collections::HashMap;

fn main() {
    let some_str = "some string";

    let hm = some_str.chars().fold(HashMap::new(), |mut hm, c| {
        match hm.get(&c) { // borrow of hm
            None => hm.insert(c, 1),
            Some(i) => { // i is a reference
                let j = *i; // i is Copy so we copy it, j is not a reference owned by hm, so hm is not borrowed anymore
                hm.insert(c, j + 1) // we can borrow hm mutable
            }
        };
        hm
    });

    for (key, val) in hm.iter() {
        println!("{}: {}", key, val);
    }
}

Further, a superior solution is to use the Entry api to get around this problem entirely with the following code:

use std::collections::HashMap;

fn main() {
    let some_str = "some string";

    let hm = some_str.chars().fold(HashMap::new(), |mut hm, c| {
        *hm.entry(c).or_insert(0) += 1;
        hm
    });

    for (key, val) in hm.iter() {
        println!("{}: {}", key, val);
    }
}
Captain_Obvious
  • 520
  • 5
  • 15
0

I think some_str.chars().counts(); is the easiest way to do this.

It's a built in function but the implementation is

    #[cfg(feature = "use_std")]
    fn counts(self) -> HashMap<Self::Item, usize>
    where
        Self: Sized,
        Self::Item: Eq + Hash,
    {
        let mut counts = HashMap::new();
        self.for_each(|item| *counts.entry(item).or_default() += 1);
        counts
    }
financial_physician
  • 1,672
  • 1
  • 14
  • 34