How do I create a HashMap literal?

后端 未结 6 1381
再見小時候
再見小時候 2020-11-29 21:11

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

hashmap = {
   \'element0\': {
       \'name\': \'My New Element\',
       \'childs\':          


        
相关标签:
6条回答
  • 2020-11-29 21:16

    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:

    • https://nim-lang.org/docs/tables.html#toTable,openArray[]
    • https://ruby-doc.org/core/Array.html#method-i-to_h
    0 讨论(0)
  • 2020-11-29 21:27

    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();
    
    0 讨论(0)
  • 2020-11-29 21:28

    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.
    0 讨论(0)
  • 2020-11-29 21:32

    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:

    • Add hashmap, hashset, treemap, and treeset macros
    0 讨论(0)
  • 2020-11-29 21:33

    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.

    0 讨论(0)
  • 2020-11-29 21:35

    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();
    
    0 讨论(0)
提交回复
热议问题