Meta programming is useful for reducing the amount of code you have to write and maintain, which is also one of the roles of functions. However, macros have some additional powers that functions don’t.
Macros, on the other hand, can take a variable number of parameters: we can call print! A function can’t, because it gets called at runtime and a trait needs to be implemented at compile time.
The downside to implementing a macro instead of a function is that macro definitions are more complex than function definitions because you’re writing Rust code that writes Rust code. Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere.
Macro in the standard library includes code to reallocate the correct amount of memory up front. That code is an optimization that we don’t include here to make the example simpler.
And the name of the macro we’re defining without the exclamation mark. The name, in this case DEC, is followed by curly brackets denoting the body of the macro definition.
;, the code generated that replaces this macro call will be the following: We’ve defined a macro that can take any number of arguments of any type and can generate code to create a vector containing the specified elements.
In the future, Rust will have a second kind of declarative macro that will work similarly but fix some of these edge cases. The three kinds of procedural macros (custom derive, attribute-like, and function-like) all work similarly.
This is for complex technical reasons that we hope to eliminate in the future. The Downstream type is defined by the pro_ macro crate that is included with Rust and represents a sequence of tokens.
The function also has an attribute attached to it that specifies which kind of procedural macro we’re creating. We can have multiple kinds of procedural macros in the same crate.
Let’s look at the different kinds of procedural macros. We’ll start with a custom derive macro and then explain the small dissimilarities that make the other forms different.
Additionally, we can’t yet provide the hello_ macro function with default implementation that will print the name of the type the trait is implemented on: Rust doesn’t have reflection capabilities, so it can’t look up the type’s name at runtime. We need a macro to generate code at compile time.
At the time of this writing, procedural macros need to be in their own crate. However, the way we’ve structured the project makes it possible for programmers to use hello_ macro even if they don’t want the derive functionality.
Note that this code won’t compile until we add a definition for the impl_hello_ macro function. Notice that we’ve split the code into the hello_ macro _derive function, which is responsible for parsing the Downstream, and the impl_hello_ macro function, which is responsible for transforming the syntax tree: this makes writing a procedural macro more convenient.
We’ve introduced three new crates: pro_ macro, syn, and quote. The pro_ macro crate comes with Rust, so we didn’t need to add that to the dependencies in Cargo.Tom.
The syn crate parses Rust codes from a string into a data structure that we can perform operations on. The quote crate turns syn data structures back into Rust code.
These crates make it much simpler to parse any sort of Rust code we might want to handle: writing a full parser for Rust code is no simple task. The hello_ macro _derive function will be called when a user of our library specifies # on a type.
The hello_ macro _derive function first converts the input from a Downstream to a data structure that we can then interpret and perform operations on. The fields of this struct show that the Rust code we’ve parsed is a unit struct with the dent (identifier, meaning the name) of Pancakes.
There are more fields on this struct for describing all sorts of Rust code; check the syn documentation for DeriveInput for more information. But before we do, note that the output for our derive macro is also a Downstream.
Listing 19-33: Implementing the HelloMacro trait using the parsed Rust code We get an Dent struct instance containing the name (identifier) of the annotated type using AST.dent.
The struct in Listing 19-32 shows that when we run the impl_hello_ macro function on the code in Listing 19-30, the dent we get will have the dent field with a value of “Pancakes”. Thus, the name variable in Listing 19-33 will contain an Dent struct instance that, when printed, will be the string “Pancakes”, the name of the struct in Listing 19-30.
Macro ’s execution, so we need to convert it to a Downstream. We do this by calling the into method, which consumes this intermediate representation and returns a value of the required Downstream type.
Macro also provides some very cool emulating mechanics: we can enter #name, and quote! You can even do some repetition similar to the way regular macros work.
We want our procedural macro to generate an implementation of our HelloMacro trait for the type the user annotated, which we can get by using #name. , macros which evaluate the expression and then turn the result into a String.
There is a possibility that the #name input might be an expression to print literally, so we use stringing! Also saves an allocation by converting #name to a string literal at compile time.
Let’s hook up these crates to the code in Listing 19-30 to see the procedural macro in action! They’re also more flexible: derive only works for structs and ends; attributes can be applied to other items as well, such as functions.
Here’s an example of using an attribute-like macro : say you have an attribute named route that annotates functions when using a web application framework: This # attribute would be defined by the framework as a procedural macro.
Macros, they’re more flexible than functions; for example, they can take an unknown number of arguments. Macros can be defined only using the match-like syntax we discussed in the section earlier.
Function-like macros take a Downstream parameter and their definition manipulates that Downstream using Rust code as the other two types of procedural macros do. This definition is similar to the custom derive macro ’s signature: we receive the tokens that are inside the parentheses and return the code we wanted to generate.
Now you have some Rust features in your toolbox that you won’t often use, but you’ll know they’re available in very particular circumstances. We’ve introduced several complex topics so that when you encounter them in error message suggestions or in other peoples’ code, you’ll be able to recognize these concepts and syntax.