使用 Rust proc 宏生成动态命名的结构实例方法

use Rust proc macros to generate dynamically named struct instance methods

我正在尝试编写一个程序宏来生成将所有 f64 字段加倍的方法。我让它在 ./src/main.rs

的单个字段中工作
use attr_macro::DoubleF64;

#[derive(DoubleF64)]
struct MyStruct {
    my_string: String,
    my_number: f64,
    my_other_number: f64,
}


fn main() {
    let mystruct = MyStruct {
        my_string: "some str".to_string(),
        my_number: 2.0,
        my_other_number: 2.0,
    };
    println!("my_number * 2: {}", mystruct.double_my_number());
}

和./proc_macro/src/lib.rs:

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, FieldsNamed};

#[proc_macro_derive(DoubleF64)]
pub fn double_f64(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input);

    let (func_name, fident) = if let syn::Data::Struct(s) = data {
        if let syn::Fields::Named(FieldsNamed { named, .. }) = s.fields {
            let f = named[1].ident.clone().unwrap();
            (format_ident!("double_{}", f), f)
        } else {
            (format_ident!(""), format_ident!(""))
        }
    } else {
        (format_ident!(""), format_ident!(""))
    };

    let output = quote! {
        impl #ident {
            // func_str.parse.unwrap();
            // fn double_f64(&self) -> f64 {
            //     self.my_number * 2.
            // }
            fn #func_name(&self) -> f64 { self.#fident * 2. }
        }
    };

    output.into()
}

但我正在努力弄清楚如何构建一个循环来生成有效的 TokenStream 以将其扩展到所有字段。这是我尝试过的:

#[proc_macro_derive(DoubleF64)]
pub fn double_f64(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input);

    let mut func_stream_vec: Vec<TokenStream> = Vec::new();

    if let syn::Data::Struct(s) = data {
        if let syn::Fields::Named(FieldsNamed { named, .. }) = s.fields {
            let fields = named.iter().map(|f| &f.ident);
            let ftypes = named.iter().map(|f| &f.ty);

            for (field, ftype) in fields.into_iter().zip(ftypes) {
                if stringify!(#ftype) == "f64" {
                    let fname = format_ident!("double_{}", field.clone().unwrap());
                    func_stream_vec
                        .push(quote! { fn #fname(&self) -> f64 { self.#field * 2.0 } }.into());
                }
            }
        }
    };

    let output = quote! {
        impl #ident {
            #(#func_stream_vec)*
        }
    };

    output.into()
}

src/main.rs:

// much of this code is bowrrowed from https://blog.logrocket.com/procedural-macros-in-rust/

use proc_macro::DoubleF64;

#[derive(DoubleF64)]
struct MyStruct {
    my_string: String,
    my_number: f64,
    my_other_number: f64,
}

fn main() {
    let mystruct = MyStruct {
        my_string: "some str".to_string(),
        my_number: 2.0,
        my_other_number: 17.0,
    };
    println!("my_number * 2: {}", mystruct.double_my_number());
    println!("my_other_number * 2: {}", mystruct.double_my_other_number());
}

proc_macro/src/lib.rs:

extern crate proc_macro2;
use proc_macro2::TokenStream as TokenStream2;
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, FieldsNamed, Type};

extern crate quote;
extern crate syn;

#[proc_macro_derive(DoubleF64)]
pub fn double_f64(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input);

    let mut func_stream = TokenStream2::default();

    if let syn::Data::Struct(s) = data {
        if let syn::Fields::Named(FieldsNamed { named, .. }) = s.fields {
            let fields = named.iter().map(|f| &f.ident);
            let ftypes = named.iter().map(|f| &f.ty);

            for (field, ftype) in fields.into_iter().zip(ftypes.into_iter()) {
                match ftype {
                    Type::Path(type_path)
                        if type_path.clone().into_token_stream().to_string() == "f64" =>
                    {
                        let fname = format_ident!("double_{}", field.clone().unwrap());
                        func_stream.extend::<TokenStream2>(
                            quote! { fn #fname(&self) -> f64 { self.#field * 2.0 } },
                        );
                    }
                    _ => {}
                };
            }
        }
    };

    let output = quote! {
        impl #ident {
            #func_stream
        }
    };

    output.into()
}

生成:

    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/use_attr_macro`
my_number * 2: 4
my_other_number * 2: 34

https://github.com/calbaker/rust_proc_macro_play/tree/8afb5e088d6db81e98a2aa3f31f7831dc1e3746e