Rust 从对的迭代器中收集哈希图

Posted

技术标签:

【中文标题】Rust 从对的迭代器中收集哈希图【英文标题】:Rust Collect Hashmap from Iterator of Pairs 【发布时间】:2020-11-20 05:07:30 【问题描述】:

我们有一个 HashMap,我们对其进行迭代和映射以替换值,但遇到了一个问题,将其收集回具有不同值类型的新 HashMap。

value of type `std::collections::HashMap<std::string::String, std::string::String>`
cannot be built from `std::iter::Iterator<Item=(&std::string::String, std::string::String)>`

我们所做的基本上归结为:

let old: HashMap<String, Value> = some_origin();
let new: HashMap<String, String> = old.iter().map(|(key, value)| 
  return (key, some_conversion(value));
).collect();

同样的迭代器类型也被返回(并且不可收集),如果一个压缩两个迭代器,例如在本例中是压缩键,以及只返回转换后的值的映射。

new = old.keys().into_iter().zip(old.iter().map(|(key, value)| some_conversion(value)).collect();

【问题讨论】:

试试key.clone(),minimal reproducible example会更好 【参考方案1】:

问题在于iter() (docs) 返回一个“非消耗”迭代器,它分发对基础值 ([1]) 的引用。新的 HashMap 不能使用引用 (&amp;String) 构造,它需要值 (String)。

在您的示例中,some_conversion 似乎为值部分返回了一个新的String,因此将.clone() 应用于键就可以了:

let old: HashMap<String, Value> = some_origin();
let new: HashMap<String, String> = old.iter().map(|(key, value)| 
  return (key.clone(), some_conversion(value));
  //         ^---- .clone() call inserted
).collect();

Here 是 rust 操场上完整示例的链接。

查看编译器 [2] 的错误消息,这确实很难弄清楚。我认为对此最有帮助的是围绕 Rust 中的引用和所有权建立一种直觉,以了解引用何时正常以及何时需要拥有的值。

虽然我建议阅读 Rust 书籍中关于引用和所有权的部分,甚至更多 Rust 编程,但要点如下:

    通常,Rust 中的值只有一个所有者(例外是明确的共享所有权指针,例如 Rc)。 当一个值“按值”传递时,它被移动到新位置。这会使值的原始所有者无效。 一个值可以有多个共享引用,但是当任何共享引用存在时,该值是不可变的(不能存在或创建可变引用,因此不能修改或移动它)。 我们不能将值移出共享引用(这会使原始所有者无效,当共享引用存在时它是不可变的)。 通常,Rust 不会自动复制(用 Rust 的说法是“克隆”)值,即使它可以。相反,它拥有价值的所有权。 (例外是“Copy”类型,复制成本很低,例如i32)。 (此处不相关)也可以有一个对值的可变引用。虽然此可变引用退出,但无法创建共享引用。

这有什么帮助?

谁拥有哈希映射中的键?哈希映射可以(规则 1)! 但是我们如何将新的键值对放入哈希映射中呢?这些值被移动到哈希映射中(规则 2)。 但我们不能离开共享参考...(规则 3 + 规则 4) Rust 不想克隆值,除非我们告诉它这样做(规则 5) ...所以我们必须自己克隆它。

我希望这能给你一些直觉(我再次强烈推荐 Programming Rust)。一般来说,如果你做了一些有价值的事情,你要么获得它的所有权,要么得到一个参考。如果您取得所有权,则无法再使用拥有所有权的原始变量。如果您获得参考,则不能将所有权交给其他人(无需克隆)。而且 Rust 不会为你克隆。

[1]:文档称之为“以任意顺序访问所有键值对的迭代器。迭代器元素类型为 (&'a K, &'a V)。”忽略'a生命周期参数,可以看到元素类型为(&amp;K, &amp;V)

[2]:

13 |         .collect();
   |          ^^^^^^^ value of type `std::collections::HashMap<std::string::String, std::string::String>` cannot be built from `std::iter::Iterator<Item=(&std::string::String, std::string::String)>`
   |
   = help: the trait `std::iter::FromIterator<(&std::string::String, std::string::String)>` is not implemented for `std::collections::HashMap<std::string::String, std::string::String>`

【讨论】:

似乎确实消除了编译器错误。从抱怨迭代器到哈希图转换的错误消息中,永远不会猜到这一点。 是的,我同意这很难弄清楚。我实际上打算发布一个编辑,试图展示如何从编译器读取错误消息,但后来我不得不承认,从错误中推断实际上并不容易。有帮助的是对所有权和引用有很好的理解,这可以在引用正确和不正确时给出直觉。 @autarchprinceps:我试图解释这背后的直觉 - HTH。 如果您不再需要旧地图,我认为您也可以避免克隆并改用into_iter()【参考方案2】:

如果您不再需要旧地图,您可以使用into_iter

let new: HashMap<String, String> = old.into_iter().map(|(key, value)| 
   return (key, some_conversion(value));
).collect();

你可以看到一个工作版本here

【讨论】:

以上是关于Rust 从对的迭代器中收集哈希图的主要内容,如果未能解决你的问题,请参考以下文章

在迭代器中找到第一个特定的枚举变体并对其进行转换

迭代器操作问题

Python中迭代器&生成器的“奇技淫巧“

Python中迭代器&生成器的“奇技淫巧“

Rust中的迭代器的使用:map转换filter过滤fold聚合chain链接

如何从异步迭代器中获取常规迭代器?