An introduction to macros in Rust, from my experiences
I haven’t been using Rust for a long time, but one thing I was always eager to explore was the language’s macro system. I’ve used them before, mostly in Lisp, but Rust is a very different language.
So, what is a macro? Simply put, it’s a source transformation which occurs at compile time. This has the benefit of making complex or repetitive code more readable in a generic manner, and is the application of macros that we’ll be looking at today.
Macros also enable a phenomenon you might call metaprogramming or even a domain specific language which can be used to augment the experience of writing new code in a way not conventionally achievable with the facilities of the language. This is beyond the scope of this post, however a follow-up may be made if I find an appropriate usage case.
So let’s jump right in and take a look at some code. I’ve prepared this post’s example from some code I wrote for pretty-ls. It’s pretty specific but it’s the most prominent piece of Rust I’ve written so far where a macro has been a necessity.
UNIX and permissions
To keep it short, UNIX file permissions are an integer best expressed in octal. And this is reflected in the code via the
permissions variable, of type
u32. The UNIX permissions integer contains values such as who can read, write and execute the file. I’ve made a table below of all the modes we care about and their constant names.
Okay. So we’re ready to look at our first piece of terrible code. That’s great!
So in this below snippet, we’re checking whether
permissions contains the constant
we want by bitwise-ANDing it and comparing with 0.
The output this section of code should produce is a coloured version of the permission flags
you may have seen in the output of
ls -l, it looks like this (with all bits present):
This jumble of conditionals handles the output for (parenthesized)
d(rwx)rwxrwx. It’s a lot of code for such a small piece of output!
What we really need is a shorthand way of checking
permissions for a flag, printing a given character in a given colour if it’s there, and printing the bold, black
- if it’s not.
We could use a function for this, but it incurs the overhead of a function call for each character then, which is pretty slow. This is where macros come in, because it’s an ideal way to solve this problem.
For more information on the syntax and usage of macros, see here.
Our macro, straight from the source code, is as follows:
And the equivalent to our series of conditionals above is now:
Much better! So let’s finish up by dissecting the macro.
Macros begin with a macro themselves─
macro_rules!, and then an identifier which serves as the name of the macro. Something important to remember is that macros are invoked with an exclamation mark appended. This helps to make it clear when an invocation is a macro and not anything else which might be callable, for example a function.
Our macro takes three parameters:
$color, also an
ident, and finally
expr (expression, in this case, a literal). The body of the macro from then on is much the same as our previous code. Remember that the identifiers used in the macro must be in scope. Macros can be locally scoped however so this isn’t a problem.
Of course, this code corresponds only to the first section of our permissions output. It’s another 3 lines for the next sgment, and another 3 for the last. But the reduction in code is definitely worth it, at one point in time there were conditionals for every individual permissions, so this is, I feel, a good example of how macros can be utilized to reduce “copy pasteable” code.
You can check out the rest of the
pretty-ls code here. Development is still underway to make it as fast and as pretty as possible.