Rust explicitly avoids the complexity of C++ template metaprogramming. The templates in Rust are based on the trait system that serves both static and dynamic polymorphism at the same time but doesn’t cover things like variadic arguments or advanced code generation patterns.

Instead, a flexible macro system that interprets a token stream and generates the required Rust code is available. This is called procedural macros and you provide them in the form of functions written in Rust and packed in a separate project that is built before the code that uses the macros so that it can be plugged into the compiler.

An additinal declarative macro system is available for simple pattern matching.

Standard library macros

You might want to get familiar with some of the standard librarary macros to see what cannot or maybe should not be done with a Rust function.

  • file!(), line!(), column!(), module_path!()
  • assert!(), assert_eq!(), assert_ne!()
  • matches!()
  • format_args!()
  • format!(), println!(), eprintln!(), writeln!()
  • dbg!()
  • stringify!(), concat!(), compile_error!()
  • vec!()

Procedural macros

An example of a procedural macro:

use quote::quote;
use proc_macro::TokenStream;

#[proc_macro]
pub fn make_function(input: TokenStream) -> TokenStream {
    let name = syn::parse::<syn::Ident>(input).unwrap();
    let output = quote! {
        fn #name() -> String {
        String::from("Hello, world!")
        }
    };
    output.into()
}

An example of a derive macro:

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(SimpleDebug)]
pub fn simple_debug_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let gen = quote! {
        impl std::fmt::Debug for #name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, stringify!(#name))
            }
        }
    };
    gen.into()
}