How do I create a HashMap literal?

43,850

Solution 1

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.

Rust 1.56

Many collections now offer conversions from an array argument using From or Into:

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);
}

This logic can be wrapped back into a macro for some syntax sugar:

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);
}

Rust 1.51

As of Rust 1.51, you can use by-value array iterators and FromIterator to collect into many kinds of collections:

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);
}

Note that in Rust 1.53, std::array::IntoIter isn't always needed.

This logic can be wrapped back into a macro for some syntax sugar:

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);
}

These solutions avoid both unneeded allocation and reallocation.

See also:

Previous versions

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 macro avoids allocating an unneeded intermediate Vec, but it doesn't use HashMap::with_capacity so there may be some useless reallocations of the HashMap as values are added. A more complicated version of the macro that counts the values is possible, but the performance benefits are probably not something most uses of the macro would benefit from.

Solution 2

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 regular macro_rules! macros.

Note that rust macros are flexible in which brackets you use for the invocation. You can use them as hashmap!{} or hashmap![] or hashmap!(). This crate suggests {} as the convention for the map & set macros, it matches their Debug output.

Macros

  • btreemap Create a BTreeMap from a list of key-value pairs
  • btreeset Create a BTreeSet from a list of elements.
  • hashmap Create a HashMap from a list of key-value pairs
  • hashset Create a HashSet from a list of elements.

Solution 3

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();

Solution 4

Starting with Rust 1.56, you can initialize a HashMap using from(), which is somewhat like having a HashMap literal. from() takes an array of key-value pairs. You can use it like this:

use std::collections::HashMap;

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

Solution 5

As noted by @Johannes in the comments, it's possible to use vec![] because:

  • Vec<T> implements the IntoIterator<T> trait
  • HashMap<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();
Share:
43,850

Related videos on Youtube

Maxim Samburskiy
Author by

Maxim Samburskiy

Updated on March 12, 2022

Comments

  • Maxim Samburskiy
    Maxim Samburskiy over 2 years

    How I can create a HashMap literal in Rust? In Python I can do it so:

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

    And in Go like this:

    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 {}
                }
            }
        }
    }
    
  • DK.
    DK. over 9 years
    There's also one in the grabbag_macros crate. You can see the source here: github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macro‌​s/….
  • Shepmaster
    Shepmaster about 6 years
    While it works in this case, it becomes needlessly expensive when using something like String instead of &str.
  • Alexander
    Alexander about 6 years
    Ah, shame. I like Swift approach to this, which abstracts literals from their types. A DictionaryLiteral can be used to initialize any type that conforms to ExpressibleByDictionaryLiteral (even though the standard library offers one such type, Dictionary)
  • jupp0r
    jupp0r over 5 years
    Sure there are runtime costs, but there are also costs associated with adding another crate dependency or with defining hard-to-understand macros. Most code is not performance critical and I find this version very readable.
  • Hutch Moore
    Hutch Moore over 4 years
    Additionally, this doesn't work for types that aren't Clone.
  • Johannes
    Johannes over 4 years
    This example can be tuned to avoid those issues, by using vec![ (name, value) ].into_iter().collect()
  • alex elias
    alex elias about 4 years
    Will this not represent the vector literal in the binary then collect it into a hashmap at runtime?
  • Stargateur
    Stargateur over 3 years
    "I think its a good option, as its essentially whats already used by Ruby and Nim" I don't understand how this is a argument ? why ruby or Nim do this would be a better argument then just "they do that so it's good"
  • Stargateur
    Stargateur over 3 years
    @jupp0r why bother write in Rust if you doesn't want performance ? I don't get this. You don't need to understand the detail implementation of a macro to use it.
  • Stargateur
    Stargateur over 3 years
    an opinion without argument is not useful in a SO answer
  • Denys Séguret
    Denys Séguret over 3 years
    A detail: calling the trait "Hash" is a bad idea: there's already a trait with that name and you'll fast encounter it when working with hashing in rust.
  • Stargateur
    Stargateur over 3 years
    I would also advice to prefer copied() to disallow type that don't implement copy, avoiding cloning for nothing, or at least make it explicit using a cloned_to_map() if you also want to be able to do that with only cloned type.
  • jupp0r
    jupp0r over 3 years
    @Stargateur because I don't do premature optimization and neither should you. It might be that your program would benefit from more optimized HashMap literal initialization, but it probably won't. If you want to get the best performance, measure and optimize the parts of your program that are on the critical path. Randomly optimizing stuff is just a bad strategy. My answer will be fine for 99.9999% of people. It's readable, compiles fast, does not introduce dependencies that increase complexity and introduce potential security vulnerabilities.
  • Stargateur
    Stargateur over 3 years
    @jupp0r "premature optimization" is not that
  • avl_sweden
    avl_sweden over 3 years
    Yeah, a syntax like hashmap![("Norway",100)] would make things more readable and would absolutely not be a sort of "premature optimization". It's even shorter to write! It really should be in the std-lib I think.
  • jupp0r
    jupp0r over 3 years
    @Stargateur optimizing anything without data-driven evidence that the optimization will yield tangible results is premature.
  • Alex
    Alex over 3 years
    With the stabilization of std::array::IntoIter in Rust 1.51, you no longer need nightly for the second method.
  • algmyr
    algmyr over 2 years
    This isn't really about prematurely optimizing, it's about not prematurely pessimizing. elegantchaos.com/2014/10/27/…
  • jupp0r
    jupp0r over 2 years
    Adding a new dependency to your project just to declare a hashmap literal is prematurely optimizing.
  • Haider
    Haider over 2 years
    Worth mentioning Rust 1.56's HashMap::from([ ("k1",1), ("k2",2) ])