Implementing getters

We will add two derives, Getters and Setters. We will start with the first one by creating the required boilerplate:

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(Getters)]
pub fn derive_getters(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = syn::parse(input).unwrap();

// Build the output
let expanded = impl_getters(&input);

// Hand the output tokens back to the compiler
expanded.into()
}


fn impl_getters(ast: &syn::DeriveInput) -> quote::Tokens {
let name = &ast.ident;
unimplemented!()
}

We will not only need the name of the structure, we will also need any further generics added to the structure and where clauses. We didn't bother with these in our previous example, but we should add them in this more complex one. Gladly, the syn crate gives us all we need.

Let's write the next piece of code in the impl_getters() function:

fn impl_getters(ast: &syn::DeriveInput) -> quote::Tokens {
use syn::{Data, Fields};

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) =
ast.generics.split_for_impl();

match ast.data {
Data::Struct(ref structure) => {
if let Fields::Named(ref fields) = structure.fields {
let getters: Vec<_> =
fields.named.iter()
.map(generate_getter)
.collect();

quote! {
impl #impl_generics #name #ty_generics
#where_clause {
#(#getters)*
}
}
} else {
panic!("you cannot implement getters for unit
or tuple structs");
}
},
Data::Union(ref _union) => {
unimplemented!("sorry, getters are not implemented
for unions yet");
}
Data::Enum(ref _enum) => {
panic!("you cannot derive getters for enumerations");
}
}
}

fn generate_getter(field: &syn::Field) -> quote::Tokens {
unimplemented!("getters not yet implemented")
}

We have a lot going on here. First, as you can see, we get the generics from the ast.generics field and we use them later in the quote! macro. We then check which type of data we have. We cannot implement getters or setters for enumerations, unit structs, or structures with no named fields, such as Foo(T), so we panic in those cases. Even though it's still not possible to derive anything for unions yet, we can specifically filter the options with the syn crate, so we just add that for potential future changes in the language.

In the case of a structure with named fields, we get a list of the fields and, for each of them, we implement the getter. For that, we map them to the generate_getter() function, defined at the bottom but still unimplemented.

Once we have the list of getters, we call the quote !{} macro to generate the tokens. As you can see, we add the generics for the impl block so that if we had any in the structure, such as Bar<T, F, G>, they would be added to the implementation.

To add all the getters from a vector, we use the #(#var)* syntax and the same as with the macro_rules!{} macro, it will add one after the other. We can use this syntax with any type implementing the IntoIterator trait, in this case, Vec<quote::Tokens>.

So, now we have to actually implement one getter. We have the generate_getter() function which receives a syn::Field, so we have all the information we need. The function will return quote::Tokens, so we will need to use the quote!{} macro inside. You can probably implement it yourself if you have been following, by checking the syn crate documentation at docs.rs. Let's see how it looks fully implemented:

fn generate_getter(field: &syn::Field) -> quote::Tokens {
let name = field.ident
.expect("named fields must have a name");
let ty = &field.ty;

quote! {
fn #name(&self) -> &#ty {
&self.#name
}
}
}

As you can see, this is really simple. We get the identifier or name of the attribute, which should exist given that we are only implementing it for structures for named fields, and then get the type of the field. We then create the getter and return a reference to the internal data.

We could improve this further by adding exceptions for types that have their borrowed counterparts, such as String or PathBuf, returning &str and Path respectively, but I don't think it's worth it.

We could also add the documentation of the field to the generated getter. For that, we would use the field.attrs variable and get the contents of the attribute named doc, which is the one that includes the documentation text. Nevertheless, it's not so easy, because the name of the attribute is stored as a path and we would need to convert it to a string. But I invite you to try it with the syn crate documentation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.104.183