diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 594ab08..ad856ab 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,17 @@ The format is based on `Keep a Changelog `
and this project adheres to `Semantic Versioning `_.
+[0.2.5]
+-------
+
+Added
+^^^^^
+
+* ``btree_map_e`` macro
+
+* ``map_e`` macro
+
+
[0.2.4]
-------
diff --git a/Cargo.toml b/Cargo.toml
index 9526ccd..6a7189c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "map-macro"
-version = "0.2.4"
+version = "0.2.5"
authors = ["jofas "]
edition = "2018"
license = "MIT"
diff --git a/README.md b/README.md
index 1359f26..ad2eafe 100644
--- a/README.md
+++ b/README.md
@@ -7,24 +7,19 @@
[![Docs](https://img.shields.io/badge/docs-latest-blue.svg)](https://docs.rs/map-macro/latest/map_macro)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
-Declarative `map!`, `set!`, `btree_map!`, `btree_set!` and
-`vec_no_clone!` macros.
+Declarative macros for initializing collections from the rust
+[standard library][std].
The `map!` macro allows for statically initializing a
-`std::collections::HashMap`.
-The same goes for the `set!` macro only for
-`std::collections::HashSet`.
-Both macros have an equivalent version using a b-tree data structure
-rather than a hashtable-based implementation, `btree_map!` and
-`btree_set!` for statically initializing `std::collections::BTreeMap`
-and `std::collections::BTreeSet`, respectively.
-The macros are equivalent to the `vec!` macro from the rust standard
-library.
+[hash map][hash map].
+`set!` is does the same, only for [hash sets][hash set].
+Both macros have an equivalent version using a b-tree data structure,
+`btree_map!` and `btree_set!`.
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`.
+It allows you to create vectors with the `vec![some_value; count]`
+syntax, without cloning `some_value`.
This crate has zero dependencies.
@@ -41,7 +36,7 @@ This crate has zero dependencies.
## Maps
-Some languages provide a neat way for creating non-empty
+Some languages provide neat syntactic sugar for creating non-empty
maps/dictionaries.
For example, in python you can create a non-empty map by running the
following code:
@@ -55,9 +50,7 @@ hello = {
}
```
-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:
+In rust, creating a non-empty hash map is not as straight-forward:
```rust
use std::collections::HashMap;
@@ -70,12 +63,11 @@ hello.insert("fr", "Bonjour");
hello.insert("es", "Hola");
```
-More less-readable boilerplate code is needed in rust to create a
-non-empty map.
+More less-readable boilerplate code is needed.
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.
+maps using the `map!` macro.
Creating the same `hello` map from the example above can be simplified
to:
@@ -90,13 +82,12 @@ let hello = map! {
};
```
-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.
+That's it.
+Looks nearly as neat as the python version, with the added benefit
+that `hello` is not mutable after we 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:
+keys and values as well, you are not limited to literals:
```rust
use map_macro::map;
@@ -117,24 +108,77 @@ let hello = map! {
};
```
-Empty maps can be created as well, but must provide type hints for the
-compiler:
-```rust
+### Explicitly typed values for trait objects
+
+As shown in the examples above, rust uses type inference to infer
+the correct type for the created hash map.
+Unfortunately, type inference alone can not detect
+[trait objects][trait objects].
+This will not work, because `rustc` is unable to figure out the
+right type when creating `hello`:
+
+```compile_fail
use std::collections::HashMap;
+use std::fmt::Debug;
+
use map_macro::map;
-let hello: HashMap<&str, &str> = map! {};
+let hello: HashMap<&str, &dyn Debug> = map! {
+ "en" => &"Hello",
+ "de" => &"Hallo",
+ "fr" => &"Bonjour",
+ "es" => &"Hola",
+};
+```
+
+The `map_e!` macro enables you to use trait objects as values through
+[type coercion][type coercion], making the example above compile
+successfully:
+
+```rust
+use std::collections::HashMap;
+use std::fmt::Debug;
+
+use map_macro::map_e;
+
+let hello: HashMap<&str, &dyn Debug> = map_e! {
+ "en" => &"Hello",
+ "de" => &"Hallo",
+ "fr" => &"Bonjour",
+ "es" => &"Hola",
+};
+```
+
+Note that you need to give an explicit type to the binding when you
+use `map_e!`, because it relies on knowing what type it should
+coerce the values to.
+Also, only values and not keys can be trait objects, because keys must
+implement the [`Hash`][hash] trait, which is not
+[object save][object safe].
-assert_eq!(hello.len(), 0);
+[`btree_map_e!`](#b-tree-based-maps-and-sets) is the equivalent to
+`map_e!` for creating a [b-tree map][b-tree map] with trait object
+values:
+
+```rust
+use std::collections::BTreeMap;
+use std::fmt::Debug;
+
+use map_macro::btree_map_e;
+
+let hello: BTreeMap<&str, &dyn Debug> = btree_map_e! {
+ "en" => &"Hello",
+ "de" => &"Hallo",
+ "fr" => &"Bonjour",
+ "es" => &"Hola",
+};
```
## 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).
+Rust has the same cumbersome creation process for creating sets.
In python you can create a set like this:
@@ -197,8 +241,7 @@ assert_eq!(x.len(), 0);
## B-tree based maps and sets
Besides hashtable-based maps and sets, rust's standard library offers
-maps and sets based on the b-tree data structure
-(`std::collections::BTreeMap` and `std::collections::BTreeSet`).
+maps and sets based on the b-tree data structure.
They offer similar functionality to their hashtable-based
counterparts.
`map-macro` provides the `btree_map!` and `btree_set!` macros to
@@ -325,6 +368,22 @@ assert_eq!(*unshared_vec[0].borrow(), 1);
assert_eq!(*unshared_vec[1].borrow(), 0);
```
+Note that `vec_no_clone!` treats the value as an expression, so you
+must provide the initialization as input directly.
+This, for example, won't work:
+
+```compile_fail
+use map_macro::vec_no_clone;
+
+struct UnclonableWrapper(u8);
+
+let a = UnclonableWrapper(0);
+
+// a will have moved into the first element of x, raising a compile
+// time error for the second element.
+let x = vec_no_clone![a; 5];
+```
+
You can also use the macro with a list of elements, like `vec!`:
```rust
@@ -340,3 +399,13 @@ let v2: Vec = vec![];
assert_eq!(v1, v2);
```
+
+
+[std]: https://doc.rust-lang.org/std/collections/index.html
+[hash map]: https://doc.rust-lang.org/std/collections/hash_map/struct.HashMap.html
+[hash set]: https://doc.rust-lang.org/std/collections/hash_set/struct.HashSet.html
+[trait objects]: https://doc.rust-lang.org/reference/types/trait-object.html
+[type coercion]: https://doc.rust-lang.org/reference/type-coercions.html
+[b-tree map]: https://doc.rust-lang.org/std/collections/struct.BTreeMap.html
+[hash]: https://doc.rust-lang.org/std/hash/trait.Hash.html
+[object safe]: https://doc.rust-lang.org/reference/items/traits.html#object-safety
diff --git a/TODO.md b/TODO.md
index 7fac892..ece14b2 100644
--- a/TODO.md
+++ b/TODO.md
@@ -7,3 +7,7 @@
* [x] `btree_set`
* [x] publish `v0.2.4`
+
+* [ ] `vec_no_clone` description about shortcomings
+
+* [ ] `map_e` and `btree_map_e` types (+ why no set equivalent)
diff --git a/src/lib.rs b/src/lib.rs
index c7f92f9..86abd16 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -41,6 +41,51 @@ macro_rules! map {
};
}
+/// Explicitly typed equivalent of [map!].
+///
+/// Set this [crate's](crate) documentation for more examples on how
+/// to use this macro.
+///
+/// **Example:**
+///
+/// ```rust
+/// use std::collections::HashMap;
+/// use std::fmt::Debug;
+///
+/// use map_macro::map_e;
+///
+/// let goodbye: HashMap<&str, &dyn Debug> = map_e! {
+/// "en" => &"Goodbye",
+/// "de" => &"Auf Wiedersehen",
+/// "fr" => &"Au revoir",
+/// "es" => &"Adios",
+/// };
+///
+/// println!("{:?}", goodbye);
+/// ```
+///
+#[macro_export]
+macro_rules! map_e {
+ (@to_unit $($_:tt)*) => (());
+ (@count $($tail:expr),*) => (
+ <[()]>::len(&[$(map_e!(@to_unit $tail)),*])
+ );
+
+ {$($k: expr => $v: expr),* $(,)?} => {
+ {
+ let mut map = std::collections::HashMap::with_capacity(
+ map_e!(@count $($k),*),
+ );
+
+ $(
+ map.insert($k, $v as _);
+ )*
+
+ map
+ }
+ };
+}
+
/// Macro for creating a [map](std::collections::BTreeMap) based on
/// a b-tree data structure.
///
@@ -76,6 +121,42 @@ macro_rules! btree_map {
};
}
+/// Explicitly typed equivalent of [btree_map!].
+///
+/// Set this [crate's](crate) documentation for more examples on how
+/// to use this macro.
+///
+/// **Example:**
+///
+/// ```rust
+/// use std::collections::BTreeMap;
+/// use std::fmt::Debug;
+///
+/// use map_macro::btree_map_e;
+///
+/// let goodbye: BTreeMap<&str, &dyn Debug> = btree_map_e! {
+/// "en" => &"Goodbye",
+/// "de" => &"Auf Wiedersehen",
+/// "fr" => &"Au revoir",
+/// "es" => &"Adios",
+/// };
+/// ```
+///
+#[macro_export]
+macro_rules! btree_map_e {
+ {$($k: expr => $v: expr),* $(,)?} => {
+ {
+ let mut map = std::collections::BTreeMap::new();
+
+ $(
+ map.insert($k, $v as _);
+ )*
+
+ map
+ }
+ };
+}
+
/// Macro for creating a [set](std::collections::HashSet).
///
/// Equivalent to the [vec!] macro for [vectors](std::vec::Vec).
@@ -146,8 +227,7 @@ macro_rules! btree_set {
};
}
-/// More flexible version of the [vec](std::vec) macro from the
-/// standard library.
+/// More flexible version of the [vec!] macro.
///
/// See this [crate's](crate) documentation for a description and more
/// examples on how to use this macro.
diff --git a/tests/tests.rs b/tests/tests.rs
index c897c82..0f1da57 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -1,4 +1,15 @@
-use map_macro::{btree_map, btree_set, map, set, vec_no_clone};
+use std::collections::{BTreeMap, HashMap};
+use std::fmt::Debug;
+
+use map_macro::{
+ btree_map, btree_map_e, btree_set, map, map_e, set, vec_no_clone,
+};
+
+#[derive(Debug)]
+struct Dyn1;
+
+#[derive(Debug)]
+struct Dyn2;
#[test]
fn map1() {
@@ -28,6 +39,24 @@ fn map2() {
assert_eq!(m[&2], "c");
}
+#[test]
+fn map_e1() {
+ let _: HashMap<&str, &dyn Debug> = map_e! {
+ "en" => &"Hello",
+ "de" => &"Hallo",
+ "fr" => &"Bonjour",
+ "es" => &"Hola",
+ };
+}
+
+#[test]
+fn map_e2() {
+ let _: HashMap<&str, &dyn Debug> = map_e! {
+ "1" => &Dyn1,
+ "2" => &Dyn2,
+ };
+}
+
#[test]
fn set1() {
let s = set! { "a", "b", "c", "d" };
@@ -84,6 +113,23 @@ fn btree_map2() {
assert_eq!(m[&2], "c");
}
+#[test]
+fn btree_map_e1() {
+ let _: BTreeMap = btree_map_e! {
+ 0 => &"a",
+ 1 => &"b",
+ 2 => &"c",
+ };
+}
+
+#[test]
+fn btree_map_e2() {
+ let _: BTreeMap<&str, &dyn Debug> = btree_map_e! {
+ "1" => &Dyn1,
+ "2" => &Dyn2,
+ };
+}
+
#[test]
fn btree_set1() {
let s = btree_set! { "a", "b", "c", "d" };