I am trying to write some toy code that stores the number of times it sees a word in a HashMap
. If the key exists, it increments a counter by one, if the key doesn't exist, it adds it with the value 1
. I instinctively want to do this with a pattern match, but I hit a borrow mutable more than once error:
fn read_file(name: &str) -> io::Result<HashMap<String, i32>> {
let b = BufReader::new(File::open(name)?);
let mut c = HashMap::new();
for line in b.lines() {
let line = line?;
for word in line.split(" ") {
match c.get_mut(word) {
Some(i) => {
*i += 1;
},
None => {
c.insert(word.to_string(), 1);
}
}
}
}
Ok(c)
}
The error I get is:
error[E0499]: cannot borrow `c` as mutable more than once at a time
--> <anon>:21:21
|
16 | match c.get_mut(word) {
| - first mutable borrow occurs here
...
21 | c.insert(word.to_string(), 1);
| ^ second mutable borrow occurs here
22 | }
23 | }
| - first borrow ends here
I understand why the compiler is grumpy: I've told it I'm going to mutate the value keyed on word
, but then the insert isn't on that value. However, the insert is on a None
, so I would have thought the compiler might have realized there was no chance of mutating c[s]
now.
I feel like this method should work, but I am missing a trick. What am I doing wrong?
EDIT: I realize I can do this using
if c.contains_key(word) {
if let Some(i) = c.get_mut(s) {
*i += 1;
}
} else {
c.insert(word.to_string(), 1);
}
but this seems horribly ugly code vs the pattern match (particularly having to do the contains_key()
check as an if, and then essentially doing that check again using Some
.
You have to use the Entry "pattern":
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
fn main() {
let mut words = vec!["word1".to_string(), "word2".to_string(), "word1".to_string(), "word3".to_string()];
let mut wordCount = HashMap::<String, u32>::new();
for w in words {
let val = match wordCount.entry(w) {
Vacant(entry) => entry.insert(0),
Occupied(entry) => entry.into_mut(),
};
// do stuff with the value
*val += 1;
}
for k in wordCount.iter() {
println!("{:?}", k);
}
}
The Entry object allows you to insert a value if its missing, or to modify it if it already exists.
https://doc.rust-lang.org/stable/std/collections/hash_map/enum.Entry.html
HashMap::entry()
is the method to use here. In most cases you want to use with Entry::or_insert()
to insert a value:
for word in line.split(" ") {
*c.entry(word).or_insert(0) += 1;
}
In case the value to be inserted need to be expensively calculated, you can use Entry::or_insert_with()
to make sure the computation is only executed when it needs to. Both or_insert
methods will probably cover all of your needs. But if you, for whatever reason, want to do something else, you can still simply match
on the Entry
enum.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With