How I can create a HashMap literal in Rust? In Python I can do it so:
hashmap = {
\'element0\': {
\'name\': \'My New Element\',
\'childs\':
I have seen a bunch of fancy solutions, but I just wanted something simple. To that end, here is a Trait:
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)
}
I think it's a good option, as it's essentially what's already used by Ruby and Nim:
As noted by @Johannes in the comments, it's possible to use vec![]
because:
Vec<T>
implements the IntoIterator<T>
traitHashMap<K, V>
implements FromIterator<Item = (K, V)>
which means you can do this:
let map: HashMap<String, String> = vec![("key".to_string(), "value".to_string())]
.into_iter()
.collect();
You can use &str
but you might need to annotate lifetimes if it's not 'static
:
let map: HashMap<&str, usize> = vec![("one", 1), ("two", 2)].into_iter().collect();
I recommend the maplit crate.
To quote from the documentation:
Macros for container literals with specific type.
use maplit::hashmap;
let map = hashmap!{
"a" => 1,
"b" => 2,
};
The maplit crate uses
=>
syntax for the mapping macros. It is not possible to use:
as separator due to syntactic the restrictions in regularmacro_rules!
macros.Note that rust macros are flexible in which brackets you use for the invocation. You can use them as
hashmap!{}
orhashmap![]
orhashmap!()
. This crate suggests{}
as the convention for the map&
set macros, it matches their Debug output.Macros
btreemap
Create aBTreeMap
from a list of key-value pairsbtreeset
Create aBTreeSet
from a list of elements.hashmap
Create aHashMap
from a list of key-value pairshashset
Create aHashSet
from a list of elements.
There isn't a map literal syntax in Rust. I don't know the exact reason, but I expect that the fact that there are multiple data structures that act maplike (such as both BTreeMap and HashMap) would make it hard to pick one.
However, you can create a macro to do the job for you, as demonstrated in Why does this rust HashMap macro no longer work?. Here is that macro simplified a bit and with enough structure to make it runnable in the playground:
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));
}
This has no additional allocation and is maximally efficient.
In a nightly version of Rust, you can avoid both unneeded allocation and the need for a macro:
#![feature(array_value_iter)]
use std::array::IntoIter;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::iter::FromIterator;
fn main() {
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);
}
This logic can then be wrapped back into a macro as well:
#![feature(array_value_iter)]
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
macro_rules! collection {
// map-like
($($k:expr => $v:expr),* $(,)?) => {
std::iter::Iterator::collect(std::array::IntoIter::new([$(($k, $v),)*]))
};
// set-like
($($v:expr),* $(,)?) => {
std::iter::Iterator::collect(std::array::IntoIter::new([$($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);
}
See also:
If you wish to initialize the map with only one element in one line (and without visible mutation in your code), you could do:
let map: HashMap<&'static str, u32> = Some(("answer", 42)).into_iter().collect();
This is thanks to the usefulness of Option being able to become an Iterator using into_iter().
In real code, you probably don't need to help the compiler with the type:
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);
}
There is also a way to chain this to have more than one element doing something like Some(a).into_iter().chain(Some(b).into_iter()).collect()
, but this is longer, less readable and probably has some optimization issues, so I advise against it.
There is an example of how to achieve this in the documentation for HashMap:
let timber_resources: HashMap<&str, i32> = [("Norway", 100), ("Denmark", 50), ("Iceland", 10)]
.iter()
.cloned()
.collect();