【问题标题】:Why does HashMap have iter_mut() but HashSet doesn't?为什么 HashMap 有 iter_mut() 而 HashSet 没有?
【发布时间】:2016-06-28 11:17:02
【问题描述】:

在 Rust 中为 HashMap 而不是 HashSet 提供 iter_mut 函数的设计原理是什么?

自己动手会不会是失礼(假设甚至可以做到)?

有一个可以缓解引起的情况

X 的先前借用发生在这里;不可变借用防止 X 的后续移动或可变借用直到借用结束

示例

An extremely convoluted example (Gist) 没有说明为什么参数传递是这样的。有一个简短的评论来解释痛点:

use std::collections::HashSet;

fn derp(v: i32, unprocessed: &mut HashSet<i32>) {
    if unprocessed.contains(&v) {

        // Pretend that v has been processed
        unprocessed.remove(&v);
    }   
}

fn herp(v: i32) {
    let mut unprocessed: HashSet<i32> = HashSet::new();
    unprocessed.insert(v);

    // I need to iterate over the unprocessed values
    while let Some(u) = unprocessed.iter().next() {

        // And them pass them mutably to another function
        // as I will process the values inside derp and
        // remove them from the set.
        //
        // This is an extremely convoluted example but
        // I need for derp to be a separate function
        // as I will employ recursion there, as it is
        // much more succinct than an iterative version.
        derp(*u, &mut unprocessed);
    }   
}

fn main() {
    println!("Hello, world!");
    herp(10);
}

声明

while let Some(u) = unprocessed.iter().next() {

是一个不可变的借用,因此

derp(*u, &mut unprocessed);

是不可能的,因为未处理的不能可变地借用。在 while 循环结束之前,不可变借用不会结束。

我曾尝试使用this as reference 并最终试图通过各种赋值排列、括起来的大括号来欺骗借用检查器,但由于预期表达式的耦合,问题仍然存在。

【问题讨论】:

  • 注意while let Some(u) = unprocessed.iter().next()等价于for u in &amp;unprocessed
  • 你这里还有一个问题:你不能在迭代 unprocessed 时可变地借用它/持有对它的引用。为什么不简单地使用循环 consume unprocessed 并简化您的程序逻辑?

标签: rust standard-library idioms


【解决方案1】:

您必须考虑HashSet 究竟是什么。从HashMap::iter_mut() 获得的IterMut 仅在值部分可变:(&amp;key, &amp;mut val), ((&amp;'a K, &amp;'a mut V))

HashSet 基本上是 HashMap&lt;T, ()&gt;,因此实际值是键,如果您要修改键,则必须更新它们的哈希,否则您将得到无效的 HashMap

【讨论】:

  • 您如何建议将HashSet 替换为另一个集合。我只需要containsremoveinsert。带有自制 contains 的向量?还有一种remove 特定对象的方法,通过查找其索引或其他方法。
  • @FilipAllberg 你能给出一个代码示例来说明你真正想要做什么吗?
  • 在原帖中提供了一个精简的例子
【解决方案2】:

如果您的HashSet 包含Copy 类型,例如i32,您可以处理该值的副本以提前释放HashSet 上的借用。为此,您需要从while let 表达式中的绑定中消除所有借用。在您的原始代码中,u 的类型为 &amp;i32,并且它一直从 unprocessed 借用直到循环结束。如果我们将模式更改为Some(&amp;u),那么u 的类型就是i32,它不借用任何东西,所以我们可以随意使用unprocessed

fn herp(v: i32) {
    let mut unprocessed: HashSet<i32> = HashSet::new();
    unprocessed.insert(v);

    while let Some(&u) = unprocessed.iter().next() {
        derp(u, &mut unprocessed);
    }   
}

如果类型不是Copy 或者复制/克隆的成本太高,您可以将它们包装在RcArc 中,并在使用cloned() 迭代它们时克隆它们(克隆@987654341 @ 或 Arc 不会克隆底层值,它只是克隆 Rc 指针并增加引用计数器)。

use std::collections::HashSet;
use std::rc::Rc;

fn derp(v: &i32, unprocessed: &mut HashSet<Rc<i32>>) {
    if unprocessed.contains(v) {
        unprocessed.remove(v);
    }   
}

fn herp(v: Rc<i32>) {
    let mut unprocessed: HashSet<Rc<i32>> = HashSet::new();
    unprocessed.insert(v);

    while let Some(u) = unprocessed.iter().cloned().next() {
        // If you don't use u afterwards,
        // you could also pass if by value to derp.
        derp(&u, &mut unprocessed);
    }   
}

fn main() {
    println!("Hello, world!");
    herp(Rc::new(10));
}

【讨论】:

  • 如果类型不可复制,让循环消耗集合并让derp 将项目插入新的HashSet 可能更容易
  • 是的,但如果derp 将集合用于除删除当前项目之外的其他目的,例如如果一个项目的处理取决于还有哪些其他项目要处理(据我了解,问题中derp的实现仅用于演示目的)。
猜你喜欢
  • 1970-01-01
  • 2013-04-23
  • 2021-05-06
  • 2013-12-29
  • 2015-09-25
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 2016-10-12
相关资源
最近更新 更多