在惯用的 Rust 中更新和传递 HashMap 时如何避免密钥克隆

How to avoid key clone while updating and passing a HashMap in idiomatic Rust

我正在尝试编写一段代码来检查某个条目是否在缓存中,以及它是否不产生值。

问题是为了生成值,我想传递缓存,因为生成器可能想使用其他值,或者可能想插入自己的值。

当前代码如下:

#[derive(Default)]
pub struct R {}

#[derive(Hash, PartialEq, Eq, Clone)]
pub struct Mutation {}

fn create_mutations(s: &Mutation) -> Vec<Mutation> {
    todo!();
}

fn expand(v1: &mut R, v2: &R) {
    todo!();
}

pub fn rec_fn(cache: &mut HashMap<Mutation, R>, s: &Mutation) -> R {
    let mut results: R = R::default();

    let mutations = create_mutations(s);

    for mutation in mutations {
        if cache.get(&mutation).is_none() {
            let r = rec_fn(cache, &mutation);

            cache.insert(mutation.clone(), r);
        }

        let mutations_of_mutation = &cache[&mutation];
        expand(&mut results, mutations_of_mutation);
    }

    results
}

我遇到的问题是我的 Mutation 块需要 Clone

我想知道我是否可以用不同的方式编写缓存获取和插入的块。

一种看起来很有希望但被借用检查器关闭的方法是:

pub fn rec_fn(cache: &mut HashMap<Mutation, R>, s: &Mutation) -> R {
    let mut results: R = R::default();

    let mutations = create_mutations(s);

    for mutation in mutations {

        let mutations_of_mutation = cache.entry(mutation).or_insert_with_key(|m| rec_fn(cache, m));

        expand(&mut results, mutations_of_mutation);
    }

    results
}

出现错误:

error[E0500]: closure requires unique access to `*cache` but it is already borrowed
  --> src/main.rs:97:78
   |
97 |         let mutations_of_mutation = cache.entry(mutation).or_insert_with_key(|m| rec_fn(cache, m));
   |                                     --------------------- ------------------ ^^^        ----- second borrow occurs due to use of `*cache` in closure
   |                                     |                     |                  |
   |                                     |                     |                  closure construction occurs here
   |                                     |                     first borrow later used by call
   |                                     borrow occurs here

我明白为什么会这样。但是有没有办法在没有克隆的情况下写这个 mutation?

内循环有四步:

  1. 检查密钥是否被缓存
  2. 计算要插入的值(如有必要)
  3. 将新值插入缓存(如有必要)
  4. 通过引用传递缓存值 expand

第 1 步和第 4 步需要对 cache 的共享引用,而第 2 步和第 3 步需要对 cache 的可变引用。通过将这些步骤分开,可以避免这些借用之间的冲突。在第 3 步中,我们必须将 mutation 的所有权转移到 cache,但我们可以使用 entry API 保留对插入值的引用,而无需克隆密钥。

更新后的代码如下所示:

use std::collections::HashMap;

#[derive(Default)]
pub struct R {}

#[derive(Hash, PartialEq, Eq)]
pub struct Mutation {}

fn create_mutations(s: &Mutation) -> Vec<Mutation> {
    todo!();
}

fn expand(v1: &mut R, v2: &R) {
    todo!();
}

pub fn rec_fn(cache: &mut HashMap<Mutation, R>, s: &Mutation) -> R {
    let mut results: R = R::default();

    let mutations = create_mutations(s);

    for mutation in mutations {
        // 1. check if key is in cache
        let cached = cache.contains_key(&mutation);

        // 2. calculate value to insert, if necessary
        let to_insert = if cached {
            None // already cached, no need to re-calculate
        } else {
            Some(rec_fn(cache, &mutation))
        };

        // 3. fetch, or insert and keep a reference
        let mutations_of_mutation = match to_insert {
            None => &cache[&mutation],
            Some(r) => cache.entry(mutation).or_insert(r),
        };

        // 4. pass value on to `expand`
        expand(&mut results, mutations_of_mutation);
    }

    results
}

(playground link)