This project is purely for experimentation. It's not for production. I just want to get your reviews and suggestions on this code. I'm trying to experiment on procedural derive macros with custom attributes.
Project Structure
.
├── Cargo.toml
├── reflect-derive
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
├── lib.rs
└── main.rs
Code
Each file path (relative to the project rooot) is at the head of file as comment.
# Cargo.toml
[package]
name = "reflect"
version = "0.1.0"
edition = "2021"
[dependencies]
reflect-derive = { path = "reflect-derive" }
# reflect-derive/Cargo.toml
[package]
name = "reflect-derive"
version = "0.1.0"
edition = "2021"
[dependencies]
proc-macro2 = "1.0.56"
quote = "1.0.26"
syn = "2.0.15"
[lib]
proc-macro = true
// src/main.rs
use reflect::Reflective;
#[derive(Reflective)]
struct Foo {
a: i32,
b: u32,
c: bool,
#[reflective(hidden)]
d: String,
}
fn main() {
let foo = Foo {
a: 1,
b: 2,
c: false,
d: "Hello".to_string(),
};
println!("Name of struct: {}", foo.name());
println!("Props: {:?}", foo.props());
}
// src/lib.rs
pub use reflect_derive::Reflective;
pub trait Reflective {
fn name(&self) -> String;
fn props(&self) -> Vec<String>;
}
// reflect-derive/src/lib.rs
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::{parse_macro_input, Attribute, Data, DeriveInput, Field};
#[proc_macro_derive(Reflective, attributes(reflective))]
pub fn reflect_derive_fn(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
// get struct identifier
let struct_ident = input.ident;
let struct_ident_str = struct_ident.to_string();
// get fields
let data = input.data;
let fields = get_fields(data);
let field_idents: Vec<&Ident> = fields
.iter()
// filter `#[repetitive(hidden)]` fields
.filter(|f| !is_field_hidden(f))
// get field identifiers
.map(|f| f.ident.as_ref().unwrap())
.collect();
// stringify field identifiers
let field_ident_strs: Vec<String> = field_idents.iter().map(|f| f.to_string()).collect();
let output = quote! {
impl Reflective for #struct_ident {
// simple
fn name(&self) -> String {
String::from(#struct_ident_str)
}
// repetition
fn props(&self) -> Vec<String> {
vec![#(String::from(#field_ident_strs)),*]
}
}
};
TokenStream::from(output)
}
fn get_fields(data: Data) -> Vec<Field> {
match data {
Data::Struct(data) => data
.fields
.into_iter()
.filter(|f| f.ident.is_some())
.collect::<Vec<Field>>(),
Data::Enum(_) => panic!("Enums are not supported by reflect."),
Data::Union(_) => panic!("Unions are not supported by reflect."),
}
}
fn is_field_hidden(field: &Field) -> bool {
let ref attrs = field.attrs;
attrs.iter().any(contains_hidden_attr)
}
fn contains_hidden_attr(attr: &Attribute) -> bool {
if attr.path().is_ident("reflective") {
let mut contains_hidden = false;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("hidden") {
contains_hidden = true;
Ok(())
} else {
Err(meta.error("Unrecognized `repetitive` parameters."))
}
})
.expect("Could not parse `repetitive` attribute macro.");
contains_hidden
} else {
false
}
}
Explanation
This is kind of a reflection library to get the metadata of something, which is a struct in this case. I wanted to write it for education purposes.
There is a trait named Reflective
, and reflect_derive_fn
generates the implementation for it.
Reflective::name
is quite simple, it just returns a String
with struct's name.
Reflect::props
returns a Vec<String>
of attributes of a struct.
A field in a struct, in this case Foo
struct, might have an attribute named #[reflective(hidden)]
, which hides it from the return of Reflect::props
.
The code works as expected. I just wanted to get your suggestions if you have any. The things I don't like with this implementation are:
- Getting
#[reflective(hidden)]
seems like too much boilerplate. IS there a third party library that makes it easier? - (I will add further if I have any more questions.)
Thanks in advance.