Macros 2.0

RFC: rfcs#1584
Tracking Issue: rust#39412
Feature: #![feature(decl_macro)]

While not yet stable(or rather far from being finished), there is proposal for a new declarative macro system that is supposed to replace macro_rules! dubbed declarative macros 2.0, macro, decl_macro or confusingly also macros-by-example.

This chapter is only meant to quickly glance over the current state, showing how to use this macro system and where it differs. Nothing described here is final or complete, and may be subject to change.

Syntax

We'll do a comparison between the macro and macro_rules syntax for two macros we have implemented in previous chapters:

// This code block marked `ignore` because mdbook can't handle `#![feature(...)]`.
#![feature(decl_macro)]

macro_rules! replace_expr_ {
    ($_t:tt $sub:expr) => { $sub }
}
macro replace_expr($_t:tt $sub:expr) {
    $sub
}

macro_rules! count_tts_ {
    () => { 0 };
    ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 };
    ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 };
}
macro count_tts {
    () => { 0 },
    ($odd:tt $($a:tt $b:tt)*) => { (count_tts!($($a)*) << 1) | 1 },
    ($($a:tt $even:tt)*) => { count_tts!($($a)*) << 1 },
}

As can be seen, they look very similar, with just a few differences as well as that macros have two different forms.

Let's inspect the count_tts macro first, as that one looks more like what we are used to. As can be seen, it practically looks identical to the macro_rules version with two exceptions, it uses the macro keyword and the rule separator is a , instead of a ;.

There is a second form to this though, which is a shorthand for macros that only have one rule. Taking a look at replace_expr we can see that in this case we can write the definition in a way that more resembles an ordinary function. We can write the matcher directly after the name followed by the transcriber, dropping a pair of braces and the => token.

Syntax for invoking macros is the same as for macro_rules and function-like procedural macros, the name followed by a ! followed by the macro input token tree.

macro are proper items

Unlike with macro_rules macros, which are textually scoped and require #[macro_export](and potentially a re-export) to be treated as an item, macro macros behave like proper rust items by default.

As such, you can properly qualify them with visibility specifiers like pub, pub(crate), pub(in path) and the like.

Hygiene

Hygiene is by far the biggest difference between the two declarative macro systems. Unlike macro_rules which have mixed site hygiene, macro have definition site hygiene, meaning they do not leak identifiers outside of their invocation.

As such the following compiles with a macro_rules macro, but fails with a macro definition:

// This code block marked `ignore` because mdbook can't handle `#![feature(...)]`.
#![feature(decl_macro)]
// try uncommenting the following line, and commenting out the line right after

macro_rules! foo {
// macro foo {
    ($name: ident) => {
        pub struct $name;

        impl $name {
            pub fn new() -> $name {
                $name
            }
        }
    }
}

foo!(Foo);

fn main() {
    // this fails with a `macro`, but succeeds with a `macro_rules`
    let foo = Foo::new();
}

There may be plans to allow escaping hygiene for identifiers(hygiene bending) in the future.