想要使用模式匹配添加到 HashMap,一次多次借用可变的

Want to add to HashMap using pattern match, get borrow mutable more than once at a time

我正在尝试编写一些玩具代码来存储它在 HashMap 中看到某个单词的次数。如果该键存在,它会将计数器加一,如果该键不存在,它会将值 1 添加。我本能地想通过模式匹配来做到这一点,但我不止一次遇到了借用可变错误:

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)
}

我得到的错误是:

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

我理解为什么编译器脾气暴躁:我已经告诉它我要改变 word 上键入的值,但是插入不在那个值上。然而,插入是在 None 上,所以我认为编译器可能已经意识到现在没有机会改变 c[s]

我觉得这个方法应该有效,但我错过了一个窍门。我做错了什么?

编辑:我意识到我可以使用

来做到这一点
        if c.contains_key(word) {
            if let Some(i) = c.get_mut(s) {
                *i += 1;
            }
        } else {
            c.insert(word.to_string(), 1);
        }

但这看起来非常丑陋的代码与模式匹配(特别是必须做 contains_key() 检查,然后使用 Some.

再次检查

您必须使用条目 "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);
    }
}

Entry 对象允许您在缺少值时插入值,或者在值已存在时修改它。

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() 插入一个值:

for word in line.split(" ") {
    *c.entry(word).or_insert(0) += 1;
}

如果要插入的值需要进行昂贵的计算,可以使用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枚举。

这基本上不再是问题了。 non-lexical lifetimes (NLL), your code compiles without problems. Your example on the Playground.

NLL 是编译器推理借用的一种新方法。 NLL 已在 Rust 2018 (≥ 1.31) 中启用。它最终也计划在 Rust 2015 中启用。您可以在 this official blog post.

中阅读有关 NLL 和版本的更多信息

在这种特殊情况下,我仍然认为 (entry(word).or_insert(0)) 是最好的解决方案,只是因为它非常简洁。