on
Procedural Macros, pt. 4: Getters
As we saw earlier a getter is
a simple function to access the value of a property on a struct. This could be
by copy
, reference
, slice
etc., as our requirements may be.
To generate the getter code for each field or property, we need to
- Derive an appropriate name for the function. Prefixing
get_
to the field name makes a reasonable choice. - Determine the mechanism by which the value is to be returned. While
age
is returned bycopy
, the getter forname
returns areference
to a string slice. Other types may require different treatment. The defaults that we are going to adhere to can be found in the first part of this series. - Evaluate the right return expression based on the return type determined above.
Type Info
The first problem we are going to tackle is that of type information. Before we
can evaluate the return type and getter expression for a property, we need to
know its type. Unfortunately, the type
information we have with a
syn::Field
in synstructure::Structure
is quite limited - at least for our
purposes. This means we have to evaluate the type information ourselves. At the
moment, we are only interested in the types String
and u32
. By default, we
can treat all numeric types (u8
, u16
etc.) the same, and return them by
copy. So, let’s define an enum Type
which will indicate the type of our field.
In the above match
code we are only evaluating the syn::Type
against
String
and Numeric
types. To be of any real practical use, we’d have to
expand this to a lot more -bool
, Option<T>
etc.
GetterPropertese
To make compiling the information we need to write our getter, let’s include a few helper structs.
The above snippet generates the token streams that represent the return type and expression evaluator of our getter function.
struct GetterReturnType(...)
andstruct GetterExpr(...)
form a wrapper for theproc_macro2::TokenStream
that represents the return type and expression evaluators respectively for the getter function.fn build(field: &syn::Field) {...}
With this function we evaluate the return type and expression evaluator for a particular field. We first transform the type information contained in
syn::Field
to ourType
enum. Armed with that information, it builds the requiredTokenStream
for bothGetterReturnType
andGetterExpr
.
Come Together, Over Me
The derive_getter
function we have is largely empty. Let’s edit it to put
together all the information we have.
The code above should be largely self-explanatory.
let getter_propertese = GetterPropertese::build(&field)
We determine the right return type and expression evaluator for the field we are currently processing.
let field_ident = ...; let fn_name = ...
Here, we build the function name
get_age
for our getter. We have to useformat_ident
here as we can’t directly concatenate strings in thequote
macro.quote! { ... }
We use the
quote
macro to put together all the pieces we have into a completeTokenStream
that will represent a getter.
Finally, let’s modify fn derive_propertese()
to iterate over each field in the
struct and contain all the generated getters in a impl Person
block.
We obtain the name of the struct from the AST and use that in a quote!
macro
invocation that wraps everything up neatly in an implementation block. With this
we should be at a point where our getters are generated correctly. Well, there
is only one way to verify that. Let’s give it a test.