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
不能使用引用 (&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
生命周期参数,可以看到元素类型为(&K, &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 从对的迭代器中收集哈希图的主要内容,如果未能解决你的问题,请参考以下文章