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

时间:2015-06-15 17:35:36

标签: rust borrow-checker

我正在尝试编写一些玩具代码,用于存储它在HashMap中查看单词的次数。如果密钥存在,则将计数器递增1,如果密钥不存在,则将其添加值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 个答案:

答案 0 :(得分:12)

您必须使用条目&#34;模式&#34;:

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

答案 1 :(得分:12)

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

答案 2 :(得分:1)

这基本上不再是问题。 使用non-lexical lifetimes(NLL),您的代码可以毫无问题地编译。 Your example on the Playground

NLL是编译器提出借用原因的新方法。 NLL已在Rust 2018(≥1.31)中启用。计划最终也将在Rust 2015中启用它。您可以在this official blog post中阅读有关NLL和版本的更多信息。

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