on
Procedural Macros, pt. 1: An Introduction
Macros, declarative or procedural, are mechanisms in rust that use a few input keywords to generate blocks of code. In this series of posts, we are going to build a derive macro (a type of procedural macro), which will generate functions that get the value of a member variable in a struct. These functions are commonly known as getters (and are generally accompanied by setters).
#[derive(propertese)]
struct Person {
name: String,
age: u16,
}
Given the struct declared above, our derive
macro propertese
will generate
the code below.
impl Person {
pub fn name(&self) -> &str {
self.name.as_str()
}
pub fn age(&self) -> u16 {
self.age
}
}
While the macro invocation #[derive(propertese)]
is simple, generation of the
target code is more involved. As we work our way through this, we will find that
it has to
- Identify the name of the struct.
- Detect the types of the variables in the struct.
- Determine whether it has to return values by copy (Numerics), or as a slice (String)
- and as we get into it, a lot more…
Functional Specs
Before we begin writing the code for our macro, let’s establish the basic rules with which the macro should operate. These rules will form the basis for the functional requirements of the macro.
Getters
The return type of the getters should be specialized by the type of the member variable. We are going to keep things simple here, and only consider
String
and Numeric types. By default, the following mapping will be used.Property Type Return Type String &str T where T: Numeric T We should be able to skip the generation of a getter for any of the member variables.
For our derive
macro to be really useful, we’d have to expand our rules to
handle a variety of additional types. We are, however, going to keep things
simple and focus on the details behind writing a derive
macro - in our next
post.