Map! macro as a HashMap counter of vec!

I could not find any plans to introduce a map! macro, similar to vec!. Perhaps it's been pitched under a different name. I would like to get input on this, as I think the usefulness is perhaps obvious; I was a a bit surprised to see a lack of optics on this already.

Here's a discrete use case one might have:

    primes = map! {
     'a' => 2,
     'b' => 3,
     'c' => 5,
     'd' => 7,
     'e' => 11,
     'f' => 13,
     'g' => 17,
     'h' => 19,
     'i' => 23,
     'j' => 29,
     'k' => 31,
     'l' => 37,
     'm' => 41,
     'n' => 43,
     'o' => 47,
     'p' => 53,
     'q' => 59,
     'r' => 61,
     's' => 67,
     't' => 71,
     'u' => 73,
     'v' => 79,
     'w' => 83,
     'x' => 89,
     'y' => 97,
     'z' => 101
    };

Where someone could use this prime map to encode (lowercase, for brevity) strings, such as testing if two strings are anagrams of one another.

1 Like

I think this is just not as often needed as vec![], and with const generics we have From impls that are just as short, and don't require any macro: HashMap::from([("key", "value"), ("k2", "v2")]);

Oh, and there is a crate for that (probably more than one).

4 Likes

I'm going to contest the idea it's not needed as often. Maps and Vecs are the standard of common data structures. Think of the relative pervasiveness of {} and [] in python.

The same syntax you mention is just as short and exists for Vec (Vec::from(...)) but yet we have the vec! macro. I agree it is not as popular as Vec, but I think it deserves the macro. Behind Vec, HashMap would be the 2nd most important and used data structure.

I know this could lead to a slippery slope of "Now we need a btreemap! and a heap!", but for the time being, I think the prevalence of HashMap would justify such a macro into prelude/std.

Talk me out of it. But I still like the idea of this as a macro in the prelude.

4 Likes

Except in python, everything is a map (dict). A majority of {} uses in python shouldn't be hashmap!{} in rust, but rather Struct {}.

Two reasons for vec! to exist that don't apply to hashmap!:

  • We have array literals, and vec is the growable array type, so should have equivalent syntax.
  • vec! existed before const generics did, so Vec::from([T; N]) was limited to 32 elements.

The biggest argument is the former imho: array literals have a known syntax, but we don't have an existing syntax for maps. As such, there's multiple possible syntaxes for a map! macro: { key: val } struct-like, { key => val } match-like, [ (key, val) ] collect-like, etc.

11 Likes

Besides the two reasons @CAD97 mentioned, there are another two:

  1. vec![] also has the repeat syntax [v; n], map! won't.
  2. Vecs are still far more common than maps.
2 Likes

Unlikely, because it will require parentheses around expressions. But the point still holds.

I think I end up using BTreeMap more myself. In my experience, I have vectors of static data in literal code way more than maps. And the times I do, the from family of functions work fine (though vectors of tuples can be better if it can keep the data in a few cache lines instead of spread in a web across the heap). How big of a "map literal" is being considered here?

The syntax is also up for debate. Python-like :, PHP-like =>, or something else completely?

2 Likes

This is fifth reason: we have two kinds of maps.

Not just two, an infinite number since hash map is parameterized by its hasher.

3 Likes

My instinct is to say that Vec is also parameterized by an allocator, but we replace the hasher far more than we replace tha allocator.

Not if expression type ascription doesn't exist.


Also, it's worth noting that a lot of people are moving to support the opinion that you should prefer an ordered collection over an unordered one until performance metrics indicate that the performance benefit of (effectively) nondeterministic iteration order outweighs the downsides of unpredictable ordering.

Having hashmap! but not indexmap! or btreemap! hinders this.


Ultimately what I find myself falling back to as the appropriate macro is instead based on collect/FromIterator.

macro_rules! collect {
    [$($e:expr),* $(,)?] => {
        [$($e),*].into_iter().collect()
    }
}

... but also with how little that macro does, it's not really worth having the macro over just using from or from_iter instead, which have the benefit of the inbuilt type hint.

(FromIterator is added to the prelude by edition 2021.)

2 Likes

I think you had some really solid points CAD.

However what you said last I don't agree with, particularly "how little that macro does, it's not really worth having the macro". A majority of people probably don't want to touch Rust macros with a 50 foot pole. Rust and Rust Macros are literally two different languages.

That is why people have built crates for that.

To be clear, what I'm suggesting isn't for people to rewrite the macro, but just to call from or from_iter instead. It's literally the difference between

let _: IndexMap = collect![("a", "b"), ("c", "d")];
// the type hint is required

and

let _ = IndexMap::from([("a", "b"), ("c", "d")]);
// or maybe it requires
let _ = IndexMap::from_iter([("a", "b"), ("c", "d")]);

That's not worth a macro imho.

4 Likes

There are a whole bunch of crates out there that do this, I like https://crates.io/crates/velcro

It would be nice to have a const map though. You can make your own without too much effort, I guess (if performance or ergonomics don't matter too much)

There is an example of this in the examples section of hashmap, but perhaps it should be part of the first example. Similarly in the book, hash maps are only constructed via mut/insert.

And honestly other than repeated clones vec! could just be Vec::from([a, b, c]) most of the time. I can certainly understand where someone could get the ideas that

  • to construct a Vec: use the magic/convenient vec! macro
  • to construct a Map: use mut and a bunch of verbose inserts (why is it this inconvenient)

without happening upon HashMap::from.

4 Likes