首页 > 解决方案 > 在不克隆的情况下使用闭包内的向量

问题描述

我有这个数据结构。

let bucket = HashMap<&str, Vec<&str>>

给定

let cluster = Vec<&str>

我想从VecBucket 上的 s 扩展它,我可以保证我只会访问每个键值对一次,并且&strincluster始终是 in 中的一个键bucket

use std::collections::HashMap;

fn main() {
    let mut bucket: HashMap<&str, Vec<&str>> = HashMap::new();
    bucket.insert("a", vec!["hello", "good morning"]);
    bucket.insert("b", vec!["bye", "ciao"]);
    bucket.insert("c", vec!["good"]);
    let cluster = vec!["a", "b"];
    let cluster2 = vec!["c"];
    let mut clusters = [cluster, cluster2];
    clusters.iter_mut().for_each(|cluster| {
        // I don't like this clone
        let tmp = cluster.clone();
        let tmp = tmp.iter().flat_map(|seq| bucket[seq].
            clone() // I really don't like this other clone
        );
        cluster.extend(tmp);
    });
    println!("{:?}", clusters);
}

这可以编译,但我真正想做的是耗尽向量,bucket因为我知道我不会再次访问它。

let tmp = tmp.iter().flat_map(|seq| bucket.get_mut(seq).
    unwrap().drain(..)
);

这给了我一个编译器错误:

error: captured variable cannot escape `FnMut` closure body
  --> src/main.rs:13:45
   |
4  |       let mut bucket: HashMap<&str, Vec<&str>> = HashMap::new();
   |           ---------- variable defined here
...
13 |           let tmp = tmp.iter().flat_map(|seq| bucket.get_mut(seq).
   |                                             - ^-----
   |                                             | |
   |  ___________________________________________|_variable captured here
   | |                                           |
   | |                                           inferred to be a `FnMut` closure
14 | |             unwrap().drain(..)
   | |______________________________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

我需要不安全吗?如何?更重要的是,想要删除它是否合理clone

标签: rustclosureslifetimeunsafe

解决方案


您可以消除bucket[seq].clone()使用std::mem::take()

let tmp = tmp.iter().flat_map(
    |seq| std::mem::take(bucket.get_mut(seq).unwrap()),
);

这将转移现有的所有权Vec并在哈希映射中留下一个空的。由于地图仍处于明确定义的状态,因此这是 100% 安全的。由于空向量不分配,因此它也很有效。最后,由于您可以保证不再访问该密钥,因此它是正确的。(游乐场。

正如评论中所指出的,另一种方法是从哈希图中删除向量,这也转移了向量的所有权:

let tmp = tmp.iter().flat_map(|seq| bucket.remove(seq).unwrap());

外部cluster.clone()无法替换,take()因为您需要旧内容。这里的问题是你不能扩展你正在迭代的向量,Rust 不允许这样做以实现有效的基于指针的迭代。这里一个简单而有效的解决方案是使用索引而不是迭代(操场):

clusters.iter_mut().for_each(|cluster| {
    let initial_len = cluster.len();
    for ind in 0..initial_len {
        let seq = cluster[ind];
        cluster.extend(std::mem::take(bucket.get_mut(seq).unwrap()));
    }
});

当然,使用索引你要付出间接检查和绑定检查的代价,但是 rustc/llvm 在安全的情况下非常擅长删除这两种检查,即使没有,索引访问可能仍然比克隆更有效. 确定这是否对您的原始代码有所改进的唯一方法是在生产数据上对两个版本进行基准测试。


推荐阅读