#macro-derive #proc-macro #procedural

procedural-masquerade

macro_rules for making proc_macro_derive pretending to be proc_macro

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

Download history 775/week @ 2024-06-18 728/week @ 2024-06-25 350/week @ 2024-07-02 565/week @ 2024-07-09 995/week @ 2024-07-16 759/week @ 2024-07-23 848/week @ 2024-07-30 953/week @ 2024-08-06 1106/week @ 2024-08-13 779/week @ 2024-08-20 754/week @ 2024-08-27 755/week @ 2024-09-03 664/week @ 2024-09-10 653/week @ 2024-09-17 880/week @ 2024-09-24 701/week @ 2024-10-01

2,989 downloads per month
Used in 86 crates (5 directly)

MIT/Apache

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, impls, 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:

  1. libfoo depends on libfoo-macros, and imports its macros.
  2. Everything exported by libfoo-macros (which is one custom derive) is re-exported to users of libfoo. They’re not expected to use it directly, but expansion of the foo_stringify macro needs it.
  3. This macro invocation defines yet another macro, called libfoo__invoke_proc_macro, which is also exported. This indirection is necessary because re-exporting macro_rules! macros doesn’t work currently, and once again it is used by the expansion of foo_stringify. Again, we use a long prefix to avoid name collisions.
  4. Finally, we define the macro that we really want. This one has a name that users will use.
  5. 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.
  6. 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 called ProceduralMasqueradeDummyType, as a placeholder to use derive. If libfoo__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.
  7. In addition to the dummy type, the items returned by our procedural component are inserted here. (In this case the STRINGIFIED constant.)
  8. 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 like return or continue, 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.

No runtime deps