bluss/maplit

Specific HashMap type

kurikomoe opened this issue · 1 comments

TL;DR
This issue suggests the crate providing a new hashmap_ex! macro which allows user to specify the HashMap<K, V> type.

let map = hashmap_ex!(
    HashMap<_, i32>,
    {
        "One", 1,
        "Two", 2,
    }
)

// expaned as 
let map = {
    let _cap = <[()]>::len(&[(), ()]);
    let mut _map: HashMap<_, i32> = ::std::collections::HashMap::with_capacity(_cap);
    let _ = _map.insert("One", 1);
    let _ = _map.insert("Two", 2);
    _map
};

Full:
Recently I came cross a problem that hashmap! cannot be used to init a HashMap<_, Box<dyn Trait>> object.

#[macro_use] extern crate maplit;
use std::collections::HashMap;

struct Foo;
struct Bar;

trait Zoo {}

impl Zoo for Foo {}
impl Zoo for Bar {}

fn main() {
    let mm: HashMap<_, Box<dyn Zoo>> = hashmap!(
        "Good" => Box::new(Foo {}),
        "Bad"  => Box::new(Bar {}),
    );
}

The compiler complains that

error[E0308]: mismatched types
   --> src/main.rs:15:28
    |
15  |         "Bad"  => Box::new(Bar {}),
    |                   -------- ^^^^^^ expected struct `Foo`, found struct `Bar`
    |                   |
    |                   arguments to this function are incorrect
    |
.........
    |
199 |     pub fn new(x: T) -> Self {
    |            ^^^

error[E0308]: mismatched types
  --> src/main.rs:13:40
   |
13 |       let mm: HashMap<_, Box<dyn Zoo>> = hashmap!(
   |  ________________________________________^
14 | |         "Good" => Box::new(Foo {}),
15 | |         "Bad"  => Box::new(Bar {}),
16 | |     );
   | |_____^ expected trait object `dyn Zoo`, found struct `Foo`
   |
   = note: expected struct `HashMap<_, Box<dyn Zoo>>`
              found struct `HashMap<&str, Box<Foo>>`

after using cargo expand to examine the generated code, I found out that the hashmap! strongly depend on the compiler's type infer mechanics.

#[macro_use]
extern crate maplit;
use std::collections::HashMap;
struct Foo;
struct Bar;
trait Zoo {}
impl Zoo for Foo {}
impl Zoo for Bar {}
fn main() {
    let mm: HashMap<_, Box<dyn Zoo>> = {
        let _cap = <[()]>::len(&[(), ()]);
        // ERROR:  At this time, the compiler cannot know the exact type of the HashMap.
        let mut _map = ::std::collections::HashMap::with_capacity(_cap);    
        let _ = _map.insert("Good", Box::new(Foo {}));
        let _ = _map.insert("Bad", Box::new(Bar {}));
        _map
    };
}

To avoid this, current hashmap! macro needs users to write the following code:

let mm = hashmap!(
    "Good" => Box::new(Foo {}) as Box<dyn Zoo>,
    "Bad"  => Box::new(Bar {}) as Box<dyn Zoo>,
);

So, I suggest a new macro hashmap_ex! which add a $t:ty in its parameters, which you can see in the patch that comes along with this issue.

let map = hashmap_ex!(
    HashMap<_, i32>,
    {
        "One", 1,
        "Two", 2,
    }
)

Possible Problems

  1. The {} is required for the data, aka, you cannot use () nor [] to wrap the data.
hashmap_ex!(
    HashMap<_, _>,
    {    // <--- the brace is forced to be "{}" 
       "One" => 1,
       "Two" => 2,
    }
)
  1. The convert_args is not working for hashmap_ex!

Also useful if you need HashMaps with a specific Hasher, for testing I use a deterministic Hasher which has a different type than the standard HashMap:

hashmap_ex!(
    HashMap<_, _, BuildHasherDefault<DefaultHasher>>,
    {
        ...
    }
)

DefaultHasher
BuildHasherDefault
(standard HashMap has type: HashMap<_, _, RandomState>)