Exercise 10: Macros Calling Macros

We briefly mentioned in a previous exercise that macros are able to call other macros. In this exercise we will look at a brief example of that. Before we do, there are three small notes we should mention.

Useful built-in macros

There are two useful macros which the standard library provides - stringify!() and concat!(). Both of them produce static string slices, made up of tokens.

The stringify! macro takes tokens and turns them into a &str that textually represents what those tokens are. For example, stringify!(1 + 1) will become "1 + 1".

The concat! macro takes a comma-separated list of literals, and creates a &str which concatenates them. For example, concat!("test", true, 99) becomes "testtrue99".

It's useful to know that if either of these have a macro in their parameter, (i.e. stringify!(test!())), the internal macro will be expanded first. So, if test!() expanded to 1 + 1, your string would be "1 + 1", not "test!()".

The tt fragment specifier

An important macro specifier which we have not, as of yet, discussed, is the tt macro. This captures a "Token Tree", which is any token, or a group of tokens inside brackets. This is the most flexible fragment specifier, because it imposes no meaning on what the captured tokens might be. For example:

macro_rules! stringify_number {
    (one) => {"1"};
    (two) => {"2"};
    ($tokens:tt) => { stringify!($tokens)};
}

fn main() {
stringify_number!(one); // is "1"
stringify_number!(while); // is "while"
stringify_number!(bing_bang_boom); // is "bing_bang_boom"
}

It's really important to keep in mind with tt macros that you must ensure that anything after them can be unambiguously parsed.

In other words, the metavariable $($thing:tt)* (ending with *, + OR ?) must be the last fragment in the parser. Since anything can be a token tree, Rust could not know what to accept after that parser.

To avoid this issue, you can either match a single tt, and make the user wrap multiple tokens inside brackets, or you can specify a delimiter for your match (i.e. $($thing:tt),+, since two token trees not separated by a , could not match).

Restrictions on "Forwarding Macros"

There is one important restriction when calling a macro using another macro.

When forwarding a matched fragment to another macro-by-example, matchers in the second macro will be passed an AST of the fragment type, which cannot be matched on except as a fragment of that type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens. The following illustrates this restriction:

macro_rules! foo {
    ($l:expr) => { bar!($l); }
// ERROR:               ^^ no rules expected this token in macro call
}

macro_rules! bar {
    (3) => {}
}

fn main() {
foo!(3);
}

The following illustrates how tokens can be directly matched after matching a tt fragment:

// compiles OK
macro_rules! foo {
    ($l:tt) => { bar!($l); }
}

macro_rules! bar {
    (3) => {}
}

fn main() {
foo!(3);
}

Exercise 10: Macros Calling Macros

In this exercise, you have already been provided with a macro called digit, which maps the identifiers zero through nine to a &str with their numeric value.

Your task is to write a macro called number!() which takes at least one of the identifiers zero through nine, and converts them to a string containing numbers.

For example, number!(one two three) should expand to "123".

Note: previously exercise 10 was about making a hashmap. The exercise has changed, but the old code is still available in the archive/ directory. It will be removed on the next update of this book.