The term metaprogramming in Rust often overlaps with the term macros. There are two primary types of macros available in Rust:
Both types of macros take as input an abstract syntax tree (AST), and produce one or more AST.
A commonly used macro is println. A variable number of arguments and types are joined with the format string through the use of a macro to produce formatted output. To invoke recursive macros like this, invoke the macro just like a function with the addition of a ! before the arguments. Macro applications may alternatively be surrounded by [] or {}:
vec!["this is a macro", 1, 2];
Recursive macros are defined by macro_rules! statements. The inside of a macro_rules definition is very similar to that of a pattern-matching expression. The only difference is that macro_rules! matches syntax instead of data. We can use this format to define a reduced version of the vec macro. This is shown in the following code snippet, in intro_metaprogramming.rs:
macro_rules! my_vec_macro
{
( $( $x:expr ),* ) =>
{
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
}
}
This definition accepts and matches only one pattern. It expects a comma-separated list of expressions. The syntax pattern ( $( $x: expr ),* ) matches against a comma-separated list of expressions and stores the result in the plural variable $x. In the body of the expression, there is a single block. The block defines a new vec, then iterates through $x* to push each $x into the vec, and, finally, the block returns the vec as its result. The macro and its expansion are as follows, in intro_metaprogramming.rs:
//this
my_vec_macro!(1, 2, 3);
//is the same as this
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
It is important to note that expressions are moved as code, not as values, so side effects will be moved to the evaluating context, not the defining context.
Recursive macro patterns match against token strings. It is possible to execute separate branches depending on which tokens are matched. A simple case match looks like the following, in intro_metaprogramming.rs:
macro_rules! my_macro_branch
{
(1 $e:expr) => (println!("mode 1: {}", $e));
(2 $e:expr) => (println!("mode 2: {}", $e));
}
fn main()
{
my_macro_branch!(1 "abc");
my_macro_branch!(2 "def");
}
The name recursive macros comes from recursion in the macros, so of course we can call into the macro that we are defining. Recursive macros could be a quick way to define a domain-specific language. Consider the following code snippet, in intro_metaprogramming.rs:
enum DSLTerm {
TVar { symbol: String },
TAbs { param: String, body: Box<DSLTerm> },
TApp { f: Box<DSLTerm>, x: Box<DSLTerm> }
}
macro_rules! dsl
{
( ( $($e:tt)* ) ) => (dsl!( $($e)* ));
( $e:ident ) => (DSLTerm::TVar {
symbol: stringify!($e).to_string()
});
( fn $p:ident . $b:tt ) => (DSLTerm::TAbs {
param: stringify!($p).to_string(),
body: Box::new(dsl!($b))
});
( $f:tt $x:tt ) => (DSLTerm::TApp {
f: Box::new(dsl!($f)),
x: Box::new(dsl!($x))
});
}
The second form of macro definitions is procedural macros. Recursive macros can be thought of as a nice syntax to help define procedural macros. Procedural macros, on the other hand, are the most general form. There are many things you can do with procedural macros that are simply impossible with the recursive form.
Here, we can grab the TypeName of a struct and use that to automatically generate a trait implementation. Here is the macro definition, in intro_metaprogramming.rs:
#![crate_type = "proc-macro"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
#[proc_macro_derive(TypeName)]
pub fn type_name(input: TokenStream) -> TokenStream
{
// Parse token stream into input AST
let ast = syn::parse(input).unwrap();
// Generate output AST
impl_typename(&ast).into()
}
fn impl_typename(ast: &syn::DeriveInput) -> quote::Tokens
{
let name = &ast.ident;
quote!
{
impl TypeName for #name
{
fn typename() -> String
{
stringify!(#name).to_string()
}
}
}
}
The corresponding macro invocation looks like the following, in intro_metaprogramming.rs:
#[macro_use]
extern crate metaderive;
pub trait TypeName
{
fn typename() -> String;
}
#[derive(TypeName)]
struct MyStructA
{
a: u32,
b: f32
}
As you can see, procedural macros are a bit more complicated to set up. However, the benefit is then that all processing is done directly with normal Rust code. These macros permit use of any syntactic information in unstructured format to generate more code structures before compilation.
Procedural macros are handled as separate modules to be precompiled and executed during normal compiler execution. The information provided to each macro is localized, so
whole program consideration is not possible. However, the available local information is sufficient to achieve some fairly complicated effects.