8 releases
Uses old Rust 2015
0.1.7 | Jun 20, 2020 |
---|---|
0.1.6 | Jun 14, 2018 |
0.1.5 | Nov 11, 2017 |
0.1.3 | Oct 17, 2017 |
0.1.1 | Feb 28, 2017 |
#1150 in Rust patterns
2,989 downloads per month
Used in 86 crates
(5 directly)
13KB
88 lines
Custom derive
pretending to be functional procedural macros on Rust 1.15
This crate enables creating function-like macros (invoked as foo!(...)
)
with a procedural component,
based on both custom derive
(a.k.a. Macros 1.1) and macro_rules!
.
This convoluted mechanism enables such macros to run on stable Rust 1.15, even though functional procedural macros (a.k.a. Macros 2.0) are not available yet.
A library defining such a macro needs two crates: a “normal” one, and a proc-macro
one.
In the example below we’ll call them libfoo
and libfoo-macros
, respectively.
Credits
The trick that makes this crate work is based on an idea from David Tolnay. Many thanks!
Example
As a simple example, we’re going to re-implement the stringify!
macro.
This is useless since stringify!
already exists in the standard library,
and a bit absurd since this crate uses stringify!
internally.
Nevertheless, it serves as a simple example to demonstrate the use of this crate.
The proc-macro
crate
The minimal Cargo.toml
file is typical for Macros 1.1:
[package]
name = "libfoo-macros"
version = "1.0.0"
[lib]
proc-macro = true
In the code, we define the procedural part of our macro in a function.
This function will not be used directly by end users,
but it still needs to be re-exported to them
(because of limitations in macro_rules!
).
To avoid name collisions, we include a long and explicit prefix in the function’s name.
The function takes a string containing arbitrary Rust tokens,
and returns a string that is parsed as items.
The returned string can contain constants, statics, functions, impl
s, etc.,
but not expressions directly.
#[macro_use] extern crate procedural_masquerade;
extern crate proc_macro;
define_proc_macros! {
#[allow(non_snake_case)]
pub fn foo_internal__stringify_const(input: &str) -> String {
format!("const STRINGIFIED: &'static str = {:?};", input)
}
}
A less trivial macro would probably use
the syn
crate to parse its input
and the quote
crate to generate its output.
The library crate
[package]
name = "libfoo"
version = "1.0.0"
[dependencies]
libfoo-macros = {path = "./macros", version = "1.0"}
#[macro_use] extern crate libfoo_macros; // (1)
pub use libfoo_macros::*; // (2)
define_invoke_proc_macro!(libfoo__invoke_proc_macro); // (3)
#[macro_export]
macro_rules! foo_stringify { // (4)
( $( $tts: tt ) ) => {
{ // (5)
libfoo__invoke_proc_macro! { // (6)
foo_internal__stringify_const!( $( $tts ) ) // (7)
}
STRINGIFIED // (8)
}
}
}
Let’s go trough the numbered lines one by one:
libfoo
depends onlibfoo-macros
, and imports its macros.- Everything exported by
libfoo-macros
(which is one customderive
) is re-exported to users oflibfoo
. They’re not expected to use it directly, but expansion of thefoo_stringify
macro needs it. - This macro invocation defines yet another macro, called
libfoo__invoke_proc_macro
, which is also exported. This indirection is necessary because re-exportingmacro_rules!
macros doesn’t work currently, and once again it is used by the expansion offoo_stringify
. Again, we use a long prefix to avoid name collisions. - Finally, we define the macro that we really want. This one has a name that users will use.
- The expansion of this macro will define some items,
whose names are not hygienic in
macro_rules
. So we wrap everything in an extra{…}
block to prevent these names for leaking. - Here we use the macro defined in (3),
which allows us to write something that look like invoking a functional procedural macro,
but really uses a custom
derive
. This will define a type calledProceduralMasqueradeDummyType
, as a placeholder to usederive
. Iflibfoo__invoke_proc_macro!
is to be used more than once, each use needs to be nested in another block so that the names of multiple dummy types don’t collide. - In addition to the dummy type,
the items returned by our procedural component are inserted here.
(In this case the
STRINGIFIED
constant.) - Finally, we write the expression that we want the macro to evaluate to.
This expression can use parts of
foo_stringify
’s input, it can contain control-flow statements likereturn
orcontinue
, and of course refer to procedurally-defined items.
This macro can be used in an expression context. It expands to a block-expression that contains some items (as an implementation detail) and ends with another expression.
For users
Users of libfoo
don’t need to worry about any of these implementation details.
They can use the foo_stringify
macro as if it were a simle macro_rules
macro:
#[macro_use] extern crate libfoo;
fn main() {
do_something(foo_stringify!(1 + 2));
}
fn do_something(_: &str) { /* ... */ }
More
To see a more complex example, look at
cssparser
’s src/macros.rs
and
cssparser-macros
’s macros/lib.rs
.