From 2cb6bd96ac4ee97b2c6ac17bc652dae917cc9f2b Mon Sep 17 00:00:00 2001 From: jofas Date: Mon, 29 Jan 2024 16:52:28 +0100 Subject: [PATCH] explicit macros equivalent for every macro and doc enhancements --- CHANGELOG.md | 10 ++++ README.md | 2 + src/_std.rs | 145 ++++++++++++++++++++++++++++++++++++++++++++--- src/hashbrown.rs | 60 ++++++++++++++++++-- src/lib.rs | 96 ++++++++++++++++++++++++++----- 5 files changed, 286 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb26684..0bf57b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `hashbrown::hash_set` macro +* `hashbrown::hash_set_e` macro + +* `binary_heap_e` macro + +* `btree_set_e` macro + +* `hash_set_e` macro + +* `vec_no_clone_e` macro + ### Changed * Explicitly typed map macros: keys also cast now (before only values were diff --git a/README.md b/README.md index 4b827ed..bcabbfb 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ let hello = hash_map! { "de" => "Hallo", "fr" => "Bonjour", "es" => "Hola", + "cat" => "Hola", + "🌍" => "👋", }; ``` diff --git a/src/_std.rs b/src/_std.rs index 8e8990e..f67c13b 100644 --- a/src/_std.rs +++ b/src/_std.rs @@ -12,6 +12,7 @@ /// "de" => "Auf Wiedersehen", /// "fr" => "Au revoir", /// "es" => "Adios", +/// "cat" => "Adéu", /// }; /// ``` /// @@ -22,8 +23,9 @@ macro_rules! hash_map { }; } -/// Explicitly typed equivalent of [`hash_map!`], suitable for -/// [trait object values](crate#explicitly-typed-values-for-trait-objects). +/// Explicitly typed equivalent of [`hash_map!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. /// /// # Examples /// @@ -38,6 +40,7 @@ macro_rules! hash_map { /// "de" => &"Auf Wiedersehen", /// "fr" => &"Au revoir", /// "es" => &"Adios", +/// "cat" => &"Adéu", /// }; /// /// println!("{:?}", goodbye); @@ -64,6 +67,7 @@ macro_rules! hash_map_e { /// "de" => "Auf Wiedersehen", /// "fr" => "Au revoir", /// "es" => "Adios", +/// "cat" => "Adéu", /// }; /// ``` /// @@ -74,8 +78,9 @@ macro_rules! btree_map { }; } -/// Explicitly typed equivalent of [`btree_map!`], suitable for -/// [trait object values](crate#explicitly-typed-values-for-trait-objects). +/// Explicitly typed equivalent of [`btree_map!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. /// /// # Examples /// @@ -90,6 +95,7 @@ macro_rules! btree_map { /// "de" => &"Auf Wiedersehen", /// "fr" => &"Au revoir", /// "es" => &"Adios", +/// "cat" => &"Adéu", /// }; /// ``` /// @@ -121,6 +127,31 @@ macro_rules! hash_set { }; } +/// Explicitly typed equivalent of [`hash_set!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. +/// +/// # Examples +/// +/// ```rust +/// use std::collections::HashSet; +/// +/// use map_macro::hash_set_e; +/// +/// enum Foo { A, B, C, D } +/// +/// let x: HashSet = hash_set_e! { Foo::A, Foo::B, Foo::C, Foo::C, Foo::D }; +/// +/// assert_eq!(x.len(), 4); +/// ``` +/// +#[macro_export] +macro_rules! hash_set_e { + {$($v: expr),* $(,)?} => { + ::std::collections::HashSet::from([$($v as _,)*]) + }; +} + /// Macro for creating a [`BTreeSet`](::std::collections::BTreeSet). /// /// Syntactic sugar for [`BTreeSet::from`](::std::collections::BTreeSet::from). @@ -142,6 +173,31 @@ macro_rules! btree_set { }; } +/// Explicitly typed equivalent of [`btree_set!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. +/// +/// # Examples +/// +/// ```rust +/// use std::collections::BTreeSet; +/// +/// use map_macro::btree_set_e; +/// +/// enum Foo { A, B, C, D } +/// +/// let x: BTreeSet = btree_set_e! { Foo::A, Foo::B, Foo::C, Foo::C, Foo::D }; +/// +/// assert_eq!(x.len(), 4); +/// ``` +/// +#[macro_export] +macro_rules! btree_set_e { + {$($v: expr),* $(,)?} => { + ::std::collections::BTreeSet::from([$($v as _,)*]) + }; +} + /// Macro for creating a [`VecDeque`](::std::collections::VecDeque). /// /// Follows the same syntax as the [`vec!`](::std::vec!) macro. @@ -173,8 +229,9 @@ macro_rules! vec_deque { }; } -/// Explicitly typed equivalent of [`vec_deque!`], suitable for -/// [trait object values](crate#explicitly-typed-values-for-trait-objects). +/// Explicitly typed equivalent of [`vec_deque!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. /// /// # Examples /// @@ -237,8 +294,9 @@ macro_rules! linked_list { }; } -/// Explicitly typed equivalent of [`linked_list!`], suitable for -/// [trait object values](crate#explicitly-typed-values-for-trait-objects). +/// Explicitly typed equivalent of [`linked_list!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. /// /// # Examples /// @@ -301,6 +359,41 @@ macro_rules! binary_heap { }; } +/// Explicitly typed equivalent of [`binary_heap!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. +/// +/// # Examples +/// +/// ``` +/// use std::collections::BinaryHeap; +/// +/// use map_macro::binary_heap_e; +/// +/// enum Foo { A, B, C, D } +/// +/// let v: BinaryHeap = binary_heap_e![Foo::A, Foo::B, Foo::C, Foo::D]; +/// let v: BinaryHeap = binary_heap_e![Foo::A; 4]; +/// ``` +/// +#[macro_export] +macro_rules! binary_heap_e { + {$v: expr; $c: expr} => { + { + let mut bh = ::std::collections::BinaryHeap::with_capacity($c); + + for _ in 0..$c { + bh.push($v as _); + } + + bh + } + }; + {$($v: expr),* $(,)?} => { + ::std::collections::BinaryHeap::from([$($v as _,)*]) + }; +} + /// Version of the [`vec!`](::std::vec!) macro where the value does not have to implement [`Clone`]. /// /// Useful for unclonable types or where `Clone` is exerting undesired behaviour. @@ -439,6 +532,7 @@ macro_rules! binary_heap { #[macro_export] macro_rules! vec_no_clone { {$v: expr; $c: expr} => { + { let mut vec = Vec::with_capacity($c); @@ -455,3 +549,38 @@ macro_rules! vec_no_clone { } }; } + +/// Explicitly typed equivalent of [`vec_no_clone!`]. +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. +/// +/// # Examples +/// +/// ``` +/// use std::fmt::Display; +/// +/// use map_macro::vec_no_clone_e; +/// +/// let v: Vec<&dyn Display> = vec_no_clone_e![&0; 4]; +/// ``` +/// +#[macro_export] +macro_rules! vec_no_clone_e { + {$v: expr; $c: expr} => { + + { + let mut vec = Vec::with_capacity($c); + + for _ in 0..$c { + vec.push($v as _); + } + + vec + } + }; + {$($v: expr),* $(,)?} => { + { + vec![$($v as _),*] + } + }; +} diff --git a/src/hashbrown.rs b/src/hashbrown.rs index 2979a09..af44d8a 100644 --- a/src/hashbrown.rs +++ b/src/hashbrown.rs @@ -1,6 +1,21 @@ //! Macros for initializing [`hashbrown`] maps and sets. //! -//! # Supported versions of `hashbrown` +//! # Example +//! +//! ``` +//! use map_macro::hashbrown::hash_map; +//! +//! let hello = hash_map! { +//! "en" => "Hello", +//! "de" => "Hallo", +//! "fr" => "Bonjour", +//! "es" => "Hola", +//! "cat" => "Hola", +//! "🌍" => "👋", +//! }; +//! ``` +//! +//! # Supported Versions of `hashbrown` //! //! As of writing this, up to the current `hashbrown` version `0.14` **all** //! versions of `hashbrown` are supported. @@ -15,9 +30,10 @@ //! macros from this module would break for that new version. //! //! **Note:** to be compatible with all versions of `hashbrown` at once, this -//! crate doesn't re-export `hashbrown`, which means that if you rename -//! `hashbrown` in your dependencies, the macros from this module won't be able -//! to import the needed types resulting in a compile-time error. +//! crate doesn't re-export `hashbrown`. +//! That means that (I) you need to specify it as a dependency yourself and +//! (II) you can't rename it or the macros from this module won't be able to +//! import the needed types, resulting in a compile-time error. //! /// Macro for creating a [`HashMap`](::hashbrown::HashMap). @@ -34,6 +50,7 @@ /// "de" => "Auf Wiedersehen", /// "fr" => "Au revoir", /// "es" => "Adios", +/// "cat" => "Adéu", /// }; /// ``` /// @@ -45,8 +62,9 @@ macro_rules! __hb_hash_map { }; } -/// Explicitly typed equivalent of [`hash_map!`](self::hash_map), suitable for -/// [trait object values](crate#explicitly-typed-values-for-trait-objects). +/// Explicitly typed equivalent of [`hash_map!`](self::hash_map). +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. /// /// # Examples /// @@ -62,6 +80,7 @@ macro_rules! __hb_hash_map { /// "de" => &"Auf Wiedersehen", /// "fr" => &"Au revoir", /// "es" => &"Adios", +/// "cat" => &"Adéu", /// }; /// /// println!("{:?}", goodbye); @@ -97,6 +116,32 @@ macro_rules! __hb_hash_set { }; } +/// Explicitly typed equivalent of [`hash_set!`](self::hash_set). +/// +/// See the [explicity typed macros](crate#explicitly-typed-macros) section. +/// +/// # Examples +/// +/// ```rust +/// use hashbrown::HashSet; +/// +/// use map_macro::hashbrown::hash_set_e; +/// +/// enum Foo { A, B, C, D } +/// +/// let x: HashSet = hash_set_e! { Foo::A, Foo::B, Foo::C, Foo::C, Foo::D }; +/// +/// assert_eq!(x.len(), 4); +/// ``` +/// +#[doc(hidden)] +#[macro_export] +macro_rules! __hb_hash_set_e { + {$($v: expr),* $(,)?} => { + <::hashbrown::HashSet::<_> as ::core::iter::FromIterator<_>>::from_iter([$($v as _,)*]) + }; +} + #[doc(inline)] pub use __hb_hash_map as hash_map; @@ -105,3 +150,6 @@ pub use __hb_hash_map_e as hash_map_e; #[doc(inline)] pub use __hb_hash_set as hash_set; + +#[doc(inline)] +pub use __hb_hash_set_e as hash_set_e; diff --git a/src/lib.rs b/src/lib.rs index 89edb41..7a8d66a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ #![doc = include_str!("../README.md")] //! -//! ## Explicitly Typed Values for Trait Objects +//! ## Explicitly Typed Macros //! -//! As shown in the example above, the compiler uses type inference to infer the correct type -//! for the created map. -//! Unfortunately, type inference alone can not detect [trait objects][trait objects]. +//! As shown in the example above, the compiler uses type inference to infer the +//! correct type for the created map. +//! Unfortunately, type inference alone sometimes isn't enough. +//! For example, it can't detect [trait objects][trait objects]. //! This will not work, because the compiler is unable to figure out the right type: //! //! ```compile_fail @@ -18,6 +19,8 @@ //! "de" => &"Hallo", //! "fr" => &"Bonjour", //! "es" => &"Hola", +//! "cat" => &"Hola", +//! "🌍" => "👋", //! }; //! ``` //! @@ -35,23 +38,90 @@ //! "de" => &"Hallo", //! "fr" => &"Bonjour", //! "es" => &"Hola", +//! "cat" => &"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 safe][object safe]. +//! The macro above uses `as`—Rust's [casting operator]—to cast the +//! provided keys and values to the right type. +//! It pretty much desugars to: +//! +//! ``` +//! use std::collections::HashMap; +//! use std::fmt::Debug; +//! +//! use map_macro::hash_map; +//! +//! let hello: HashMap<&str, &dyn Debug> = hash_map! { +//! "en" as _ => &"Hello" as _, +//! "de" as _ => &"Hallo" as _, +//! "fr" as _ => &"Bonjour" as _, +//! "es" as _ => &"Hola" as _, +//! "cat" as _ => &"Hola" as _, +//! "🌍" as _ => &"👋" as _, +//! }; +//! ``` +//! +//! This means that all kinds of casts and coercions are supported, including +//! non-capturing closures to function pointer casts: +//! +//! ```rust +//! use std::collections::HashMap; +//! use std::fmt::Debug; +//! +//! use map_macro::hash_map_e; +//! +//! let how_are_you: HashMap<&str, fn() -> &'static str> = hash_map_e! { +//! "en" => || "How are you?", +//! "de" => || "Wie geht's dir?", +//! "fr" => || "Comment vas-tu?", +//! "es" => || "¿Cómo estás?", +//! "cat" => || "Com estàs?", +//! }; +//! ``` +//! +//! Or casting to convert enums to `u8`: +//! +//! ```rust +//! use std::collections::HashMap; +//! use std::fmt::Debug; +//! +//! use map_macro::hash_map_e; +//! +//! enum SupportedLocales { +//! En, +//! De, +//! Fr, +//! Es, +//! Cat, +//! } +//! +//! // using enum to integer cast for the keys here +//! let welcome: HashMap = hash_map_e! { +//! SupportedLocales::En => "Welcome", +//! SupportedLocales::De => "Willkommen", +//! SupportedLocales::Fr => "Bienvenue", +//! SupportedLocales::Es => "Bienvenido", +//! SupportedLocales::Cat => "Benvingut", +//! }; +//! +//! assert_eq!(welcome[&0], "Welcome"); +//! assert_eq!(welcome[&1], "Willkommen"); +//! assert_eq!(welcome[&2], "Bienvenue"); +//! assert_eq!(welcome[&3], "Bienvenido"); +//! assert_eq!(welcome[&4], "Benvingut"); +//! ``` +//! +//! Note that you need to give an explicit type to the binding when you use +//! `hash_map_e!`, because it relies on knowing what type it should coerce the +//! values to. //! -//! Where the trait bounds on generic type parameters of the collections allow trait objects, -//! macros for explicitly typed variants are provided. //! The explicitly typed versions of the macros are indicated by an `_e` suffix. //! //! [trait objects]: https://doc.rust-lang.org/reference/types/trait-object.html //! [type coercion]: https://doc.rust-lang.org/reference/type-coercions.html -//! [object safe]: https://doc.rust-lang.org/reference/items/traits.html#object-safety -//! [hash]: https://doc.rust-lang.org/std/hash/trait.Hash.html +//! [casting operator]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#type-cast-expressions //! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]