Rust macro system and declarative macro

In this post, it is about to use declarative macros to make DSL and variadic interface.

Rust Macros

Fundamentally, macros are a way of writing code that writes other code, which is known as metaprogramming.

Rust has powerful macro system what is almost compiler plugin level. The reason this is possible is that rust allows you to handle Abstract Syntax Tree, not just strings at preprocessor like C/C++.

There are 5-types macros.

With them, we can make several techniques like below.

Declarative macros

Domain Specific Languages

Macros runs a way of pattern matching on source code. The patterns are compared with the structure of that source code and the code associated with each pattern, when matched, replaces the code passed to the macro. This all happens during compilation.

Let’s see the example which contains DSL technique.

macro_rules! calculate {
    (sum $e1:expr, $e2:expr) => {
        let var1: i32 = $e1;
        let var2: i32 = $e2;
        println!("The sum is = {}", var1 + var2);
    };
    (mul $e1:expr, $e2:expr) => {
        let var1: i32 = $e1;
        let var2: i32 = $e2;
        println!("The mul is = {}", var1 * var2);
    };
}

fn main() {
    calculate! {
        sum 1, 2
    }

    calculate! {
        mul 3, 4
    }
}
The sum is = 3
The mul is = 12

There are two branches. One is sum and another is mul.

Within () is $name:expr which matches any Rust expression. And it gives matched expression the name as $name. The each branch of example takes two expression $e1 and $e2.

We can force types to expression like let var1: usize = $e1.

+ Variadic

Let’s take variadic arguments on above example. I will only apply it to sum to compare.

macro_rules! calculate {
    (sum $($e:expr),*) => {
        let mut sum: i32 = 0;
        $(
            let elem: i32 = $e;
            sum += elem;
        )*
        println!("The sum is = {}", sum);
    };
    (mul $e1:expr, $e2:expr) => {
        let var1: i32 = $e1; // Force types to be integers
        let var2: i32 = $e2;
        println!("The mul is = {}", var1 * var2);
    };
}

fn main() {
    calculate! {
        sum 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
    }

    calculate! {
        mul 3, 4
    }
}
The sum is = 55
The mul is = 12

The code looks a little weird but the code frame is simple. It runs like regular expression.

1: $($e:expr),*

2: $(
3:     ...
4: )*

Line 1 takes variadic arguments by * keyword (0..n). And make loop in line 2~4 for n times.

So, the code is unfolded like below.

/// 0..
let elem: i32 = 1;
sum += elem;

let elem: i32 = 2;
sum += elem;

...

/// ..n
let elem: i32 = 10;
sum += elem;

Summary

Those examples are about DSL and variadic using declarative macro. Macros looks like a little weird, but it is very usable. If we use procedural macros together, we can make more powerful code. (compiler-plugin level!!) In the next post, let’s look at procedural macros.

References