The idiomatic way to do what you're doing is to use a derive macro. You defined a new macro, let's call it
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(PrintFields)]
pub fn derive_print_fields(item: TokenStream) -> TokenStream {
todo!()
}
In that macro, you parse the explicitly defined fields of the struct (probably using syn
, though simple cases can be parsed manually), and generate the required functions and trait impls based on the struct's definition.
In your specific case, perhaps a simple #[derive(Debug)]
is already good enough for your needs? It prints all fields, but in an unstable debugging-oriented format.
Types are critical for working with Rust. The emphasis on types is much greater than in something like C or C++. For example, certain language-level properties, like possibility of uninitialized memory (MaybeUninit
) or mutation under &T
references (UnsafeCell
) are encoded via types. For this reason obscuring the definitions of your datatypes with macros is generally a bad idea. The definition must be maximally explicit, but it's fine to generate methods or trait impls for types.
Your specific example is sufficiently simple that you can directly implement it via macro_rules!
:
macro_rules! derive_print {
($v: vis struct $name: ident {
$($fv: vis $field: ident : $T: ty),* $(,)?
}) => {
$v struct $name {
$(
$fv $field: $T
),*
}
impl $name {
pub fn print(&self) {
$(
println!("{}: {}", stringify!($field), self.$field);
)*
}
}
};
}
derive_print!{
struct Struct {
pub one: i32,
two: i32,
three: i32,
}
}
fn main() {
let s = Struct {
one: 1,
two: 2,
three: 3,
};
s.print();
}
However, that's quite complex and brittle. For example, the definition above doesn't support fieldsless structs (struct Foo;
) or tuple structs (struct Foo(u32, bool);
). There are various helper libraries which make it easier to write procedural macros, and the macro_rules_attribute
crate can make the above code a bit more elegant. It would look like this:
use macro_rules_attribute::derive;
macro_rules! PrintFields {
($_v: vis struct $name: ident {
$($_fv: vis $field: ident : $_T: ty),* $(,)?
}) => {
impl $name {
pub fn print(&self) {
$(
println!("{}: {}", stringify!($field), self.$field);
)*
}
}
};
}
#[derive(PrintFields!)]
struct Struct {
pub one: i32,
two: i32,
three: i32,
}
But generally, in simple cases I would prefer to write the code out by hand. Unless your macro is sufficiently useful and reusable to be extracted into a separate crate, it's likely not worth it to maintain it.