To learn how custom derive macros work in Rust, I decided to create a simple derive macro to provide getters and setters for a given struct.
It would be used like this:
#[derive(GetSet)]
pub struct Example {
#[get]
#[set]
field1: String,
#[get]
field2: i32,
}
However, there are some things that feel wrong to me about it:
- The code uses both
proc_macro
andproc_macro2
, is this necessary? - Currently, every getter / setter is in their own
impl
block. But thequote!
macro requires code that is well-formed regarding braces, so I think I cannot createimpl #name {
first and fill in the methods afterwards. Is there a better way? - The function identifiers are created with
format!
, is this okay? - Would it be possible to adapt it so one could just write
#[get, set]
field: (),
instead of
#[get]
#[set]
field: (),
Apart from that, what else would you improve or change?
Here is the code:
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::{Data, DeriveInput};
#[proc_macro_derive(GetSet, attributes(get, set))]
pub fn get_set_derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let mut gen = proc_macro2::TokenStream::new();
let name = &ast.ident;
let data = &ast.data;
if let Data::Struct(data_struct) = data {
let fields = &data_struct.fields;
for field in fields {
let field_name = field
.ident
.as_ref()
.expect("struct idents are required for this to work!");
let field_type = &field.ty;
for attr in &field.attrs {
let path = attr.path();
if path.is_ident("get") {
let fn_ident = Ident::new(&format!("get_{field_name}"), Span::call_site());
let getter = quote! {
impl #name {
pub fn #fn_ident(&self) -> &#field_type {
&self.#field_name
}
}
};
gen.extend(getter);
}
if path.is_ident("set") {
let fn_ident = Ident::new(&format!("set_{field_name}"), Span::call_site());
let setter = quote! {
impl #name {
pub fn #fn_ident(&mut self, new: #field_type) {
self.#field_name = new;
}
}
};
gen.extend(setter);
}
}
}
} else {
panic!("no support for enums or unions!")
}
gen.into()
}