Another aspect of Rust is the ability to do metaprogramming—basically programming programming—using macros! Macros are expanded in Rust code before compilation, which gives them more power than a regular function. The generated code can, for instance, create functions on the fly or implement traits for a structure.
These pieces of code make everyday life a lot easier by reducing the need to create and then initialize vectors, deriving the ability to clone a structure, or simply printing stuff to the command line.
This is a simplified example for the declarative vec![] macro provided in the Rust Book (second edition, Appendix D):
#[macro_export]
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$( temp_vec.push($x); )*
temp_vec
}
};
}
Declarative macros work on patterns and run code if that pattern matches; the previous example matches 0 - n expressions (for example, a number, or a function that returns a number) and inserts temp_vec.push(...) n times, iterating over the provided expressions as a parameter.
The second type, procedural macros, operate differently and are often used to provide a default trait implementation. In many code bases, the #[derive(Clone, Debug)] statement can be found on top of structures to implement the Clone and Debug traits automatically.
Later in this chapter, we are going to use a structure, FileName, to illustrate reference counting, but for printing it to the command line using the debug literal "{:?}", we need to derive Debug, which recursively prints all members to the command line:
#[derive(Debug)]
struct FileName {
name: Rc<String>,
ext: Rc<String>
}
The Rust standard library provides several macros already, and by creating custom macros, you can minimize the boilerplate code you have to write.