想要使用模式匹配向HashMap中添加元素,在同时获取可变借用时出现了多次借用的问题。

22

我正在尝试编写一些玩具代码,以在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()检查作为if语句进行,然后再次使用Some进行检查)。

3个回答

18
您需要使用条目“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


2
请注意,几乎从不需要手动进行匹配。方法or_insert()or_insert_with()提供了一种更简洁的实现方式。有关这些方法的信息,请参见A.B.的答案。 - Lukas Kalbertodt

16

HashMap::entry() 是这里要使用的方法。在大多数情况下,您需要与Entry::or_insert()一起使用来插入一个值:

for word in line.split(" ") {
    *c.entry(word).or_insert(0) += 1;
}
如果需要插入的值需要昂贵地计算,您可以使用Entry::or_insert_with()来确保仅在需要时执行计算。两种or_insert方法可能会涵盖您所有的需求。但是,如果出于任何原因,您想要做其他事情,仍然可以对Entry枚举进行简单的match操作。

如果我没有使用"or_insert",那么我提供的代码片段是否会起作用? - cflewis

4

现在这基本上已经不是问题了。有了非词法生命周期(NLL),您的代码可以无问题编译。请参阅此处以获取更多信息。示例请见Playground

NLL 是编译器处理借用的一种新方式。自 Rust 2018 (≥ 1.31) 起就启用了 NLL。计划最终也会在 Rust 2015 中启用。请参阅官方博客文章以了解更多关于 NLL 和版本的信息。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接