What is a streamlined approach to processing helper attributes in derive macros when using Rust? To illustrate what I'm looking for, I've defined a derive macro called Duplicate
that creates a new struct that contains all of the elements in the old struct that have been marked by helper attributes. Basically, it turns
#[derive(Duplicate)]
struct MyStruct {
#[dupe_me]
x : Vec <f64>,
y : bool,
#[dupe_me]
z : char,
}
into
#[derive(Debug)]
struct MyStructDuplicated {
x : Vec <f64>,
z : char,
}
The structure of the code is
./mymacro/src/lib.rs
./mymacro/Cargo.toml
./mybin/src/main.rs
./mybin/Cargo.toml
With mymacro/Cargo.toml
as
[package]
name = "mymacro"
version = "0.1.0"
authors = ["blank"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
syn = "1.0.11"
quote = "1.0.2"
proc-macro2 = "1.0.6"
and lib.rs
as
// External dependencies
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use proc_macro2;
use quote::{format_ident, quote};
use syn;
// Define a derive macro
#[proc_macro_derive(Duplicate, attributes(dupe_me))]
pub fn duplicate_macro_derive(input: TokenStream) -> TokenStream {
// Construct a representation of Rust code as a syntax tree
// that we can manipulate
let ast: syn::DeriveInput = syn::parse(input).unwrap();
// Grab the name of the struct
let name = &ast.ident;
// Find the name of members we need to duplicate
let mut duped: Vec<(proc_macro2::Ident, syn::Type)> = vec![];
match ast.data {
// Only process structs
syn::Data::Struct(ref data_struct) => {
// Check the kind of fields the struct contains
match data_struct.fields {
// Structs with named fields
syn::Fields::Named(ref fields_named) => {
// Iterate over the fields
for field in fields_named.named.iter() {
// Get attributes #[..] on each field
for attr in field.attrs.iter() {
// Parse the attribute
match attr.parse_meta().unwrap() {
// Find the duplicated idents
syn::Meta::Path(ref path)
if path
.get_ident()
.unwrap()
.to_string()
== "dupe_me" =>
{
// Save the duped elements
let item = field.clone();
duped.push((item.ident.unwrap(), item.ty))
}
_ => (),
}
}
}
}
// Struct with unnamed fields
_ => (),
}
}
// Panic when we don't have a struct
_ => panic!("Must be a struct"),
}
// Transform the marked elements into new struct fields
let duped = duped
.iter()
.fold(quote!(), |es, (name, ty)| quote!(#es#name : #ty,));
// Create the new structure
let myname = format_ident!("{}Duplicated", name);
let gen = quote! {
#[derive(Debug)]
struct #myname {
#duped
}
};
gen.into()
//panic!(gen.to_string());
}
Then, mybin/Cargo.toml
is
[package]
name = "mybin"
version = "0.1.0"
authors = ["blank"]
edition = "2018"
[dependencies]
mymacro = { path = "../mymacro" }
and main.rs
is
// Debugging for the macro expansions
#![feature(trace_macros)]
trace_macros!(false);
// External dependencies
use mymacro::Duplicate;
#[derive(Duplicate)]
struct MyStruct {
#[dupe_me]
x : Vec <f64>,
y : bool,
#[dupe_me]
z : char,
}
fn main() {
let foo = MyStruct {
x : vec!(1.2,2.3),
y : true,
z : 'e',
};
let bar = MyStructDuplicated {
x : foo.x,
z : foo.z,
};
println!("{:?}",bar);
}
This produces
$ ./target/debug/mybin
MyStructDuplicated { x: [1.2, 2.3], z: 'e' }
As such, this does work properly. At the same time, I've ignored quite a bit of error checking in lib.rs
as well as used quite a bit of code to drill down to find the helper attributes. I'm interested in understanding if there's a better way to generate such a macro.