From 9caaa7055bfc784f6e72858427133f3b22ffdc21 Mon Sep 17 00:00:00 2001 From: jofas Date: Thu, 15 Dec 2022 17:51:59 +0100 Subject: [PATCH] [btree_]map_e + doc enhancements --- CHANGELOG.rst | 11 ++++ Cargo.toml | 2 +- README.md | 139 ++++++++++++++++++++++++++++++++++++------------- TODO.md | 4 ++ src/lib.rs | 84 +++++++++++++++++++++++++++++- tests/tests.rs | 48 ++++++++++++++++- 6 files changed, 249 insertions(+), 39 deletions(-) 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" };