Exercise 3: Literal Metavariables
In the last exercise, we saw how we could change the behaviour of a macro based on text inside the brackets. This is great, but it's basically an if statement on the text inside the brackets: it's very simplistic.
Now we will introduce the concept of a "metavariable". Metavariables capture a particular part of the text inside the macro's brackets, and let you reuse it.
The syntax for a metavariable is simple. To explain the syntax, see the example below:
macro_rules! do_thing {
(print $metavar:literal) => {
println!("{}", $metavar)
};
}
The $metavar:literal
is saying that you're capturing any literal
(which is
something like 'a'
, or 3
, or "hello"
), and naming it metavar
. Then,
$metavar
inside the println!
is saying to "fill in" that space with whatever
metavar
is.
For an invocation like
macro_rules! do_thing { (print $metavar:literal) => { println!("{}", $metavar) }; } fn main() { do_thing!(print 3); }
Rust understands that metavar
means 3
. So, when doing substitution,
it starts by writing
println!("{}", $metavar);
and then substitutes 3
for $metavar
:
fn main() { println!("{}", 3); }
But what about types?
You might be wondering why we haven't said anything about the type of the literal. It turns out that the type doesn't matter during macro expansion. Rather than needing the type, Rust just needs to know what sort of syntax to expect. If you tried to provide a variable name, and you needed a literal, Rust will throw an error. If you needed a string literal, and you provided a char literal, then Rust will happily expand the code. It'll throw an error later on in the compilation process, as if you had written the expanded code.
Why do these examples avoid using macros?
The example above uses the println!
macro inside the do_thing
macro. Rust is totally fine with this! However, macrokata
tries
to avoid (as much as possible) using macros we didn't define inside
the main function. The reason for this is that, if we did use println!
you would see its expansion as well. That could be confusing, since
print("some text")
is much easier to read than
{
::std::io::_print(
::core::fmt::Arguments::new_v1(
&["some text"],
&[],
),
);
};
Exercise 3: Literal Meta-Variables
Your task is to create a macro which can perform two small bits of math:
- The syntax
math!(3 plus 5)
should expand to3 + 5
, where3
and5
could be any literal. - The syntax
math!(square 2)
should expand to2 * 2
, where2
could be any literal.
You may not edit the main
function, but it should eventually look like the
following:
fn main() {
print_result(3 + 5);
print_result(2 * 2);
}