5

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.

Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
wyer33
  • 6,060
  • 4
  • 23
  • 53
  • 2
    Just a small hint, you can skip the whole `parse_meta()` and use `attr.path.is_ident("dupe_me")` directly as you don't care about the rest of the attribute. – Roland Fredenhagen Apr 19 '22 at 19:06

0 Answers0