::macro_rules_attribute
Use declarative macros in attribute or derive position.
/* using this crate's `#[derive]` attribute */
Motivation
macro_rules! macros can be extremely powerful, but their call-site ergonomics
are sometimes not great, especially when decorating item definitions.
Indeed, compare:
foo!
to:
-
The former does not scale well, since it leads to rightward drift and "excessive" braces.
-
But on the other hand, the latter requires setting up a dedicated crate for the compiler, a
proc-macrocrate. And 99% of the time this will pull the::synand::quotedependencies, which have a non-negligible compile-time overhead (the first time they are compiled).-
note: these crates are a wonderful piece of technology, and can lead to extremely powerful macros. When the logic of the macro is so complicated that it requires a recursive
ttmuncher when implemented as amacro_rules!macro, it is definitely time to be using aprocedural macro.Anything involving
identgeneration / derivation, for instance, will very often requireprocedural macros, unless it is simple enough for::pasteto handle it.
-
Solution
With this crate's #[apply] and #[derive]
attributes, it is now possible to use proc_macro_attribute syntax to apply a
macro_rules! macro:
extern crate macro_rules_attribute;
#
#
without even depending on ::quote, ::syn or ::proc-macro2, for
fast compile times.
Examples
Nicer derives
extern crate macro_rules_attribute;
// Easily define shorthand aliases for "derive groups"
derive_alias!
/// Strongly-typed newtype wrapper around a `usize`, to be used for `PlayerId`s.
pub
;
// You can also fully define your own derives using `macro_rules!` syntax
// (handling generic type definitions may be the only finicky thing, though…)
}
)} use Into;
}
)} use From;
#
#
Have a -lite version of a proc-macro dependency that thus requires unergonomic macro_rules!?
Say you are writing a (pervasive and yet) tiny dependency within the async
ecosystem.
-
By virtue of working with
async, you'll most probably need to deal with pin-projections, and thence, with::pin-project. -
But by virtue of being (pervasive and yet) tiny, you don't want to depend on the
quote / proc-macro2 / synheavyweight[^only_full_syn_is_heavy] troika/trinity/triumvirate of more advanced proc-macro crates.
[^only_full_syn_is_heavy]: (note that only syn with the "full" features would be the truly heavyweight party)
Hence why you may reach for something such as ::pin-project-lite, and its
pin_project! macro_rules!-based polyfill of the former's #[pin_project]
attribute.
But this suddenly hinders the ergonomics of your type definitions, and, worse,
would not be composable whenever the pattern were to be repeated for some other
functionality (e.g., say a cell_project! similar macro).
Say no more! Time to #[apply] our neat trick:
extern crate macro_rules_attribute;
use ;
#
#
More ergonomic lazy_static!s
Say you had something like:
# use Sync as Logic;
#
static MY_GLOBAL: &dyn Logic = &Vec::new;
and now you want to change the value of that MY_GLOBAL to something that isn't
const-constructible, and yet would like to minimize the churn in doing so.
// (For those unaware of it, leaking memory to initialize a lazy static is
// a completely fine pattern, since it only occurs once, and thus, a bounded
// amount of times).
static MY_GLOBAL: &dyn Logic = Boxleak; // Error: not `const`!
You could directly use a lazy_static! or a OnceCell, but then the
definition of your static will now appear noisier than it needs be. It's time
for attribute-position polish!
First, define the helper around, say, OnceCell's Lazy type:
pub use lazy_init;
and now it is time to use it!:
# use Sync as Logic;
#
extern crate macro_rules_attribute;
static MY_GLOBAL: &dyn Logic = Boxleak;
#
# use lazy_init;
#
#
Debugging
An optional compilation feature, "verbose-expansions" can be used to print at
compile-time the exact output of each macro invocation from this crate:
[]
= "..."
= ["verbose-expansions"]
Features
derive aliases
#
extern crate macro_rules_attribute;
derive_alias!
- See
derive_alias!and #[derive] for more info.
cfg aliases
#
extern crate macro_rules_attribute;
attribute_alias!
Not using #[macro_use] extern crate macro_rules_attribute
If you are allergic to #[macro_use] unscoped / globally-preluded semantics,
you may not be fond of having to use:
extern crate macro_rules_attribute;
#
like this documentation pervasively does.
In that case, know that you may very well stick to using use imports:
use ;
// or even
use *;
derive_alias!
;
or even inlining the fully qualified paths (but note that the …_alias! macros
still take unqualified paths inside the definitions):
derive_alias!
;
I personally find these approaches too noisy to be worth it, despite the so gained "namespace purity", hence my not using that pattern across the rest of the examples.