如何创建 HashMap 字面值?

如何在 Rust 中创建 HashMap 文本? 在 Python 中我可以这样做:

hashmap = {
'element0': {
'name': 'My New Element',
'childs': {
'child0': {
'name': 'Child For Element 0',
'childs': {
...
}
}
}
},
...
}

在《围棋》里,像这样:

type Node struct {
name string
childs map[string]Node
}


hashmap := map[string]Node {
"element0": Node{
"My New Element",
map[string]Node {
'child0': Node{
"Child For Element 0",
map[string]Node {}
}
}
}
}
52738 次浏览

Rust 中没有映射文字语法。我不知道 一模一样的原因,但是我认为有多个数据结构(比如 BTreeMapHashMap)行为类似于映射,这使得选择一个数据结构变得很困难。

生锈1.56

现在,许多集合使用 FromInto提供数组参数的转换:

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};


fn main() {
let s = Vec::from([1, 2, 3]);
println!("{:?}", s);


let s = BTreeSet::from([1, 2, 3]);
println!("{:?}", s);


let s = HashSet::from([1, 2, 3]);
println!("{:?}", s);


let s = BTreeMap::from([(1, 2), (3, 4)]);
println!("{:?}", s);


let s = HashMap::from([(1, 2), (3, 4)]);
println!("{:?}", s);
}

对于某些语法糖,这个逻辑可以封装回宏:

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};


macro_rules! collection {
// map-like
($($k:expr => $v:expr),* $(,)?) => \{\{
core::convert::From::from([$(($k, $v),)*])
}};
// set-like
($($v:expr),* $(,)?) => \{\{
core::convert::From::from([$($v,)*])
}};
}


fn main() {
let s: Vec<_> = collection![1, 2, 3];
println!("{:?}", s);


let s: BTreeSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);


let s: HashSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);


let s: BTreeMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);


let s: HashMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);
}

生锈1.51

从 Rust 1.51开始,您可以使用按值数组迭代器和 FromIterator来收集多种类型的集合:

use std::array::IntoIter;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;


fn main() {
// Rust 1.53
let s = Vec::from_iter([1, 2, 3]);
println!("{:?}", s);


// Rust 1.51
let s = Vec::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);


let s = BTreeSet::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);


let s = HashSet::<_>::from_iter(IntoIter::new([1, 2, 3]));
println!("{:?}", s);


let s = BTreeMap::from_iter(IntoIter::new([(1, 2), (3, 4)]));
println!("{:?}", s);


let s = HashMap::<_, _>::from_iter(IntoIter::new([(1, 2), (3, 4)]));
println!("{:?}", s);
}

注意,在 Rust 1.53中,并不总是需要 std::array::IntoIter

对于某些语法糖,这个逻辑可以封装回宏:

use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};


macro_rules! collection {
// map-like
($($k:expr => $v:expr),* $(,)?) => \{\{
use std::iter::{Iterator, IntoIterator};
Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]))
}};
// set-like
($($v:expr),* $(,)?) => \{\{
use std::iter::{Iterator, IntoIterator};
Iterator::collect(IntoIterator::into_iter([$($v,)*]))
}};
}


fn main() {
let s: Vec<_> = collection![1, 2, 3];
println!("{:?}", s);


let s: BTreeSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);


let s: HashSet<_> = collection! { 1, 2, 3 };
println!("{:?}", s);


let s: BTreeMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);


let s: HashMap<_, _> = collection! { 1 => 2, 3 => 4 };
println!("{:?}", s);
}

这些解决方案避免了不必要的分配和重新分配。

参见:

以前的版本

您可以创建一个宏来完成这项工作,如 为什么这个生锈的 HashMap 宏不再工作了?所示。下面这个宏稍微简化了一点,并且有足够的结构使它成为 可以在操场上跑:

macro_rules! map(
{ $($key:expr => $value:expr),+ } => {
{
let mut m = ::std::collections::HashMap::new();
$(
m.insert($key, $value);
)+
m
}
};
);


fn main() {
let names = map!{ 1 => "one", 2 => "two" };
println!("{} -> {:?}", 1, names.get(&1));
println!("{} -> {:?}", 10, names.get(&10));
}

这个宏避免分配一个不需要的中间 Vec,但是它没有使用 HashMap::with_capacity,因此在添加值时可能会有一些无用的 HashMap重新分配。一个更复杂的计算值的宏版本是可能的,但是性能方面的好处可能不是大多数使用该宏的人都会从中受益的。

我推荐 地图板条箱。

引用文件中的话:

用于具有特定类型的容器文字的宏。

use maplit::hashmap;


let map = hashmap!{
"a" => 1,
"b" => 2,
};

Maplit 机箱对映射宏使用 =>语法。由于常规 macro_rules!宏的语法限制,不可能使用 :作为分隔符。

请注意,生锈宏是灵活的,您可以在其中使用用于调用的括号。您可以将它们用作 hashmap!{}hashmap![]hashmap!()。这个板条箱建议 {}作为 map & set 宏的约定,它与它们的 Debug 输出相匹配。

马克罗斯

  • 从键值对列表中创建一个 BTreeMap
  • 从元素列表中创建一个 BTreeSet
  • 从键值对列表中创建一个 HashMap
  • 从元素列表中创建一个 HashSet

HashMap的文件中有一个如何实现这一点的例子:

let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)]
.iter()
.cloned()
.collect();

正如@Johannes 在评论中提到的,使用 vec![]是可能的,因为:

  • Vec<T>实现 IntoIterator<T>特性
  • HashMap<K, V>实现 FromIterator<Item = (K, V)>

也就是说你可以这么做:

let map: HashMap<String, String> = vec![("key".to_string(), "value".to_string())]
.into_iter()
.collect();

您可以使用 &str,但是如果它不是 'static,您可能需要注释生命周期:

let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();

我见过很多奇妙的解决方案,但是我想要一些简单的。为了达到这个目的,这里有一个特点:

use std::collections::HashMap;


trait Hash {
fn to_map(&self) -> HashMap<&str, u16>;
}


impl Hash for [(&str, u16)] {
fn to_map(&self) -> HashMap<&str, u16> {
self.iter().cloned().collect()
}
}


fn main() {
let m = [("year", 2019), ("month", 12)].to_map();
println!("{:?}", m)
}

我认为这是一个很好的选择,因为它基本上已经被 露比妮妮使用了。

为了一个元素

如果您希望在一行中只使用一个元素初始化 map (并且在代码中没有可见的变化) ,您可以这样做:

let map: HashMap<&'static str, u32> = Some(("answer", 42)).into_iter().collect();

这是由于有用的 Option能够成为一个 Iterator使用 into_iter()

在实际代码中,您可能不需要帮助编译器处理类型:

use std::collections::HashMap;


fn john_wick() -> HashMap<&'static str, u32> {
Some(("answer", 42)).into_iter().collect()
}


fn main() {
let result = john_wick();


let mut expected = HashMap::new();
expected.insert("answer", 42);


assert_eq!(result, expected);
}

还有一种方法可以链接多个元素来执行类似于 Some(a).into_iter().chain(Some(b).into_iter()).collect()的操作,但是这样做时间更长,可读性更差,并且可能存在一些优化问题,所以我建议不要这样做。

你可以使用 velcro板条箱 * 。它类似于 maplit,正如其他答案所推荐的那样,但是集合类型更多,语法更好(至少在我看来是这样!)和更多的功能。

假设您希望使用 String而不是 &str,那么您的确切示例如下所示:

use std::collections::HashMap;
use velcro::hash_map;


struct Node {
name: String
children: HashMap<String, Node>,
}


let map = hash_map! {
String::from("element0"): Node {
name: "My New Element".into(),
children: hash_map! {
String::from("child0"): Node {
name: "child0".into(),
children: hash_map!{}
}
}
}
};

这有点难看,因为 String是如何构造的。但是通过使用自动进行转换的 hash_map_from!,可以在不改变键类型的情况下使它更加清晰一些:

use std::collections::HashMap;
use velcro::{hash_map, hash_map_from};


let map: HashMap<String, Node> = hash_map_from! {
"element0": Node {
name: "My New Element".into(),
children: hash_map_from! {
"child0": Node {
name: "child0".into(),
children: hash_map!{}
}
}
}
};

很多并不比 Go 版本更冗长。


* 充分披露: 我是这个箱子的作者

从 Rust 1.51开始,IntoIterator是为数组实现的,因此您可以使用 from_iter方法创建 HashMap,而无需进行克隆:

use std::collections::HashMap;
use std::iter::FromIterator;


// note that this type annotation is required
let mut map: HashMap<_, _> = HashMap::from_iter([("a", 1), ("b", 2), ("c", 3)]);

至于 Rust 1.56(目前是每晚) ,您可以使用 From<[(K, V); N]>实现,它甚至更简洁:

let mut map = HashMap::from([
("a", 1),
("b", 2),
("c", 3),
]);

从 Rust 1.56开始,可以使用 from()初始化 HashMap,这有点像使用 HashMap 文本。from()接受一个键-值对数组。你可以这样使用它:

use std::collections::HashMap;


fn main() {
let hashmap = HashMap::from([
("foo", 1),
("bar", 2)
]);
}