162

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 {}
            }
        }
    }
}
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Maxim Samburskiy
  • 1,811
  • 2
  • 11
  • 13

9 Answers9

198

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 6
    There's also one in the `grabbag_macros` crate. You can see the source here: https://github.com/DanielKeep/rust-grabbag/blob/master/grabbag_macros/src/lib.rs#L3-L64. – DK. Dec 21 '14 at 00:35
  • 5
    Ah, shame. I like Swift approach to this, which abstracts literals from their types. A [`DictionaryLiteral`](https://developer.apple.com/documentation/swift/dictionaryliteral) can be used to initialize any type that conforms to [`ExpressibleByDictionaryLiteral`](https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral) (even though the standard library offers one such type, [`Dictionary`](https://developer.apple.com/documentation/swift/dictionary)) – Alexander Jun 05 '18 at 01:37
  • With the stabilization of `std::array::IntoIter` in Rust 1.51, you no longer need nightly for the second method. – Alex Mar 29 '21 at 18:18
  • 2
    Worth mentioning Rust 1.56's `HashMap::from([ ("k1",1), ("k2",2) ])` – Haider Jan 25 '22 at 21:58
72

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.
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
David J.
  • 31,569
  • 22
  • 122
  • 174
  • 2
    You can do this without an external create since Rust 1.56. `let hm = HashMap::from([(1, "2"), (3, "4")]);` – Westy92 Apr 30 '23 at 02:05
50

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();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jupp0r
  • 4,502
  • 1
  • 27
  • 34
  • 5
    While it works in this case, it becomes needlessly expensive when using something like `String` instead of `&str`. – Shepmaster Apr 27 '18 at 17:07
  • 8
    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. – jupp0r Oct 15 '18 at 14:16
  • 2
    Additionally, this doesn't work for types that aren't Clone. – Hutch Moore Dec 06 '19 at 20:53
  • 17
    This example can be tuned to avoid those issues, by using `vec![ (name, value) ].into_iter().collect()` – Johannes Feb 13 '20 at 18:52
  • @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 Nov 11 '20 at 04:25
  • 3
    @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. – jupp0r Nov 17 '20 at 18:33
  • 1
    @jupp0r "premature optimization" is not that – Stargateur Nov 17 '20 at 23:08
  • 3
    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. – avl_sweden Jan 16 '21 at 13:08
  • @Stargateur optimizing anything without data-driven evidence that the optimization will yield tangible results is premature. – jupp0r Jan 19 '21 at 19:46
  • This isn't really about prematurely optimizing, it's about not prematurely pessimizing. https://elegantchaos.com/2014/10/27/premature-pessimization-and-the-like.html – algmyr Oct 28 '21 at 23:36
  • Adding a new dependency to your project just to declare a hashmap literal is prematurely optimizing. – jupp0r Oct 29 '21 at 16:59
29

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)
    ]);
}
Daniel Giger
  • 2,023
  • 21
  • 20
7

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();
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Kamil Tomšík
  • 2,415
  • 2
  • 28
  • 32
4

You can use the velcro crate*. It is similar to maplit, as recommended in other answers, but with more collection types, better syntax (in my opinion at least!) and more features.

Assuming you want to use Strings rather than &str, your exact example would look like this:

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!{}
            }
        }
    }
};

That's a bit ugly because of how Strings are constructed. But it can be made a bit cleaner, without changing the key type, by using hash_map_from! which will automatically do conversions:

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!{}
            }
        }
    }
};

Which isn't much more verbose than the Go version.


*Full disclosure: I am the author of this crate.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
4

As of Rust 1.51, IntoIterator is implemented for arrays, so you can create a HashMap with the from_iter method without cloning:

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

And as of Rust 1.56 (currently nightly), you can use the From<[(K, V); N]> implementation, which is even cleaner:

let mut map = HashMap::from([
    ("a", 1),
    ("b", 2),
    ("c", 3),
]);
Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54
1

For one element

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Stargateur
  • 24,473
  • 8
  • 65
  • 91
-3

I have seen a bunch of fancy solutions, but I 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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Zombo
  • 1
  • 62
  • 391
  • 407
  • 2
    "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 Nov 11 '20 at 04:23
  • an opinion without argument is not useful in a SO answer – Stargateur Nov 11 '20 at 05:04
  • 2
    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. – Denys Séguret Nov 11 '20 at 06:54
  • 2
    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. – Stargateur Nov 11 '20 at 06:58