#macro #hash-map #hash-set #collections #vec

map-macro

Declarative macros for statically initializing collections

4 releases

0.2.2 Jun 22, 2022
0.2.1 Mar 17, 2022
0.2.0 Sep 1, 2021
0.1.0 May 11, 2021

#109 in Rust patterns

Download history 134/week @ 2022-03-14 158/week @ 2022-03-21 66/week @ 2022-03-28 50/week @ 2022-04-04 73/week @ 2022-04-11 36/week @ 2022-04-18 78/week @ 2022-04-25 120/week @ 2022-05-02 88/week @ 2022-05-09 128/week @ 2022-05-16 103/week @ 2022-05-23 124/week @ 2022-05-30 75/week @ 2022-06-06 121/week @ 2022-06-13 75/week @ 2022-06-20 85/week @ 2022-06-27

367 downloads per month
Used in 4 crates (via fj-kernel)

MIT license

11KB

map-macro

Build Status Codecov Latest Version Downloads Docs License: MIT

Declarative map!, set! and vec_no_clone! macros.

The map! macro allows for statically initializing a std::collections::HashMap. The same goes for the set! macro only for std::collections::HashSet. The macros are equivalent to the vec! macro from the rust standard library.

The vec_no_clone is a more flexible version of the vec! macro the standard library provides. It allows you to create vectors with the vec![some_value; count], without cloning some_value.

This crate has zero dependencies.

Table of Contents

Maps

Some languages provide a neat way for creating non-empty maps/dictionaries. For example, in python you can create a non-empty map by running the following code:

hello = {
  "en": "Hello",
  "de": "Hallo",
  "fr": "Bonjour",
  "es": "Hola",
}

In rust, creating a non-empty map (rust has a built-in type in the standard library for creating hash maps std::collections::HashMap) is not as straight-forward:

use std::collections::HashMap;

let mut hello = HashMap::new();

hello.insert("en", "Hello");
hello.insert("de", "Hallo");
hello.insert("fr", "Bonjour");
hello.insert("es", "Hola");

More less-readable boilerplate code is needed in rust to create a non-empty map. Even worse, hello must be declared as mutable, even if we do not want it to be mutable after we have added our four entries. The map-macro crate offers a better way of declaring non-empty maps, with the map! macro. Creating the same hello map from the example above can be simplified to:

use map_macro::map;

let hello = map! {
  "en" => "Hello",
  "de" => "Hallo",
  "fr" => "Bonjour",
  "es" => "Hola",
};

That is it. Looks nearly as neat as the python version with the added benefit that hello is not mutable after we have created it.

The map! macro is powerful enough to create maps from non-static keys and values as well, you are not limited to literals. You can create a map like this:

use map_macro::map;

fn hello_in_french() -> &'static str {
  "Bonjour"
}

fn spanish_language_code() -> &'static str {
  "es"
}

let hello = map! {
  "en" => "Hello",
  "de" => "Hallo",
  "fr" => hello_in_french(),
  spanish_language_code() => "Hola",
};

Empty maps can be created as well, but must provide type hints for the compiler:

use std::collections::HashMap;
use map_macro::map;

let hello: HashMap<&str, &str> = map! {};

assert_eq!(hello.len(), 0);

Sets

Rust has the same cumbersome creation process for creating sets (in rust sets are provided by the standard library, too, via the std::collections::HashSet struct).

In python you can create a set like this:

x = set([1, 2, 3])

Not as neat as a map, but still quite concise. Dart even comes with syntactic sugar for creating a set:

final x = {1, 2, 3};

In rust, you would have to write:

use std::collections::HashSet;

let mut x = HashSet::new();

x.insert(1);
x.insert(2);
x.insert(3);

The set! macro provided by the map-macro crate lets you write the same code as:

use map_macro::set;

let x = set! { 1, 2, 3 };

Again, nearly as neat as dart!

The set! macro is as powerful as the map! macro:

use map_macro::set;

fn one() -> i32 {
  1
}

let x = set! { one(), 2, 3 };
use std::collections::HashSet;
use map_macro::set;

let x: HashSet<i32> = set! {};

assert_eq!(x.len(), 0);

Vectors without cloning

When using the vec![some_value; count] syntax, the type of some_value has to implement the Clone trait, because some_value is cloned count - 1 times into all the vector elements, except the first one.

This could either be undesired behavior (you don't want clones of some_value, because its type implements Clone in a way that doesn't fit your needs) or the type you wish to pre-populate your vector with doesn't implement Clone.

For example, this will result in a panic during compile time:

struct UnclonableWrapper(u8);

// panics
let x = vec![UnclonableWrapper(0); 5];

The vec_no_clone! macro takes a different approach. Instead of cloning UnclonableWrapper(0), it treats it as an expression which is called 5 times in this case. So 5 independent UnclonableWrapper objects, each with its own location in memory, are created:

use map_macro::vec_no_clone;

struct UnclonableWrapper(u8);

let x = vec_no_clone![UnclonableWrapper(0); 5];

assert_eq!(x.len(), 5);

Without vec_no_clone! you'd have to write something far less readable and more complex to reason about like this to create the same vector:

struct UnclonableWrapper(u8);

let x: Vec<UnclonableWrapper> = (0..5)
  .map(|_| UnclonableWrapper(0))
  .collect();

assert_eq!(x.len(), 5);

vec_no_clone! is not only useful for types not implementing Clone, but also for types where cloning them is not what you want. The best example would be a reference counted pointer, std::rc::Rc. When you clone an Rc instance, a new smart pointer instance referencing the same location in memory is created. If you'd rather have multiple independent reference counted pointers to different memory locations, you can use vec_no_clone! as well:

use map_macro::vec_no_clone;

use std::cell::RefCell;
use std::rc::Rc;

// simply clones the reference counted pointer for each element that 
// is not the first
let shared_vec = vec![Rc::new(RefCell::new(0)); 2];
{
  let mut first = shared_vec[0].borrow_mut();
  *first += 1;
}

assert_eq!(*shared_vec[0].borrow(), 1);

// the second element is a clone of the reference counted pointer at 
// the first element of the vector, referencing the same address in
// memory, therefore being mutated as well
assert_eq!(*shared_vec[1].borrow(), 1);

// the `vec_no_clone!` macro does not clone the object created by the
// first expression but instead calls the expression for each element 
// in the vector, creating two independent objects, each with their 
// own address in memory
let unshared_vec = vec_no_clone![Rc::new(RefCell::new(0)); 2];

{
  let mut first = unshared_vec[0].borrow_mut();
  *first += 1;
}

assert_eq!(*unshared_vec[0].borrow(), 1);

// the second element is not the same cloned reference counted
// pointer as it would be if it were constructed with the `vec!` macro
// from the standard library like it was above, therefore it is not
// mutated
assert_eq!(*unshared_vec[1].borrow(), 0);

You can also use the macro with a list of elements, like vec!, though vec! is probably faster:

use map_macro::vec_no_clone;

let v1 = vec_no_clone![0, 1, 2, 3];
let v2 = vec![0, 1, 2, 3];

assert_eq!(v1, v2);

let v1: Vec<u8> = vec_no_clone![];
let v2: Vec<u8> = vec![];

assert_eq!(v1, v2);

No runtime deps