Writing Mactos

  • Vs reflection

Because Rust compiles into a static binary and doesn't have a runtime, it cannot have a feature similar to Java's reflection. However that doesn't mean metaprogramming is impossible. Instead Rust has Macros. These are functions that run at compliation time which transform one chunk of code into another. In Rust macros can be used to implement things that fill the same role as things like: Javassist, Lombok, Spring, and AspectJ. Which is to say a normal program will almost never use directly, but it is quite common to used some library or framework that uses macros.

The most common use for Macros is to reduce boiler plate code. Some examples of this are

  • derive which elimates the need to manually write: Eq, Ord, Clone, Copy, Hash, Default, and Debug.
  • derivative whaich adds additional configuration to customize code generated by derive.
  • derive-more which automates From, Into, FromStr, Display, Index, Not, Add, and Mul.
  • derive-new which automates generating constructors.
  • derive-builder which automates generating builders.
  • serde which automatis writing serialization and deserialization code.
  • structopt which automates writing code to parse command line flags.

The other common use for Macros is to validate things at compile time. We've already seen examples of this in macros like println! and format!, but it also includes more complex validation and templating. This is a great way to prevent all sorts of security vunraibilites some examples include:

  • rocket uses macros to validate the configuration of an HTTP server at compile time.
  • diesel uses macros to validate SQL queries at compile time.
  • maud validates html templates at compile time. A side benifit to doing work at compile time rather than at runtime, is a boot to perfromance.

Macros work by running code at compilation time. The code that is run is pure Rust code, which means that it can also use Macros. So macros can even be used to write other macros. One example of this which is useful for very simple macros is defmac which is a macro, that allows you to write what looks like an anonymous function but the parameters can be anything, like a function name for example. This is useful for reducing repitition in unit tests. The more general way to write a macro is to define a function for example:

use proc_macro;
use proc_macro::TokenStream;

/// Macro for HTML templating ...
#[proc_macro]
pub fn html(input: TokenStream) -> TokenStream {
    //Read input TokenStream to parse provided arguments
    //Output a TokenStream of Rust code to replace the input with
input
}

Similarly to define an attribute that is associated with a function:

use proc_macro;
use proc_macro::TokenStream;

/// Macro for logging all calls to a function.
#[log_calls]
pub fn implementation_of_log_calls(input: TokenStream) -> TokenStream {
    //Read a TokenStream and return a modified TokenStream that logs the parameters
input
}

Or you can have a function extend derive:

use proc_macro;
use proc_macro::TokenStream;

/// Automatically generate the implementation of `SomeTrait` for your type.
#[proc_macro_derive(SomeTrait)]
pub fn some_trait_derive(input: TokenStream) -> TokenStream {
    //...
input
}

In each case the implementation needs to deal with a TokenStream. Fortunatly there are libraries to help with this. Two of the most important are:

  • syn which parses a Rust TokenStream into an abstract syntax tree and allows you to operate on structs.
  • quote which takes a code template, and inserts values into it from variables. Most macro implmentations use these two libraries.