使用Rust过程宏来生成动态命名的struct实例方法



我正试图编写一个过程宏,生成将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

最新更新