Is something like this C macro possible in Rust?

I would like to specify fields in a single place and generate code in many places automatically. I can do it in C this way (Playground):

#include <stdio.h>

#define FIELDS(MACRO) \
    MACRO(one) \
    MACRO(two) \
    MACRO(three)

#define DEFINE_FIELD(name) int name;
struct Struct {
    FIELDS(DEFINE_FIELD)
};

#define PRINT_FIELD(name) printf(#name ": %d\n", s.name);
void print(struct Struct s) {
    FIELDS(PRINT_FIELD)
}

int main() {
    struct Struct s = {1, 2, 3};
    print(s);
    return 0;
}
one: 1
two: 2
three: 3

I've tried for a while but got nowhere. I wanted to ask whether this is possible with Rust macros.

This is how ChatGPT translated the code into Rust (non-functional Playground):

macro_rules! fields {
    ($macro:ident) => {
        $macro!(one);
        $macro!(two);
        $macro!(three);
    };
}

macro_rules! define_field {
    ($name:ident) => {
        pub field $name: i32,
    };
}

macro_rules! print_field {
    ($name:ident) => {
        println!("{}: {}", stringify!($name), self.$name);
    };
}

struct Struct {
    fields!(define_field)
}

impl Struct {
    fn print(&self) {
        fields!(print_field)
    }
}

fn main() {
    let s = Struct { one: 1, two: 2, three: 3 };
    s.print();
}
1 Like

Rust restricts the places where macro calls can appear syntactically. They can only be used for complete

  • items[1] and/or statements; (can be multiple ones from a single macro call)
  • expressions
  • types
  • patterns

This means that e.g. you cannot expand to "a list of field declarations" because that's a different kind of syntactical element. You would need to make sure that the full macro expands to a permitted thing; e.g., you could expand to the whole struct declaration.

This is probably easiest by in-turn making use of something that Rust supports but C does not: Macros that take a whole variable-length list of arguments. Just make $macro! { … } receive the whole list of names at once, and it can use them in flexible ways:

macro_rules! fields {
    ($macro:ident) => {
        $macro! {one two three}
    };
}

macro_rules! define_fields {
    ($($name:ident)*) => {
        struct Struct {
            $(
                pub $name: i32,
            )*
        }
    };
}

fields! {define_fields}

impl Struct {
    fn print(&self) {
        // macro defined in here so that `self` can be used without going against macro hygiene
        macro_rules! print_fields {
            ($($name:ident)*) => {
                $(
                    println!("{}: {}", stringify!($name), self.$name);
                )*
            };
        }
        fields! {print_fields}
    }
}

fn main() {
    let s = Struct {
        one: 1,
        two: 2,
        three: 3,
    };
    s.print();
}

Rust Playground


  1. including: associated items ↩︎

5 Likes

Perfect answer, thank you very much!

By the way, it would also be feasible to further abstract the "define helper macro and use it pattern", e.g.:

macro_rules! with_fields {
    ($macro:ident) => {
        $macro! {one two three}
    };
}

macro_rules! fields {
    ($helper:ident: $dollar:tt $name:ident => $($t:tt)*) => {
        macro_rules! $helper {
            ($dollar($dollar$name:ident)*) => {
                $($t)*
            };
        }
        with_fields! { $helper }
    };
}

fields! { define_fields: $name =>
    struct Struct {
        $(
            pub $name: i32,
        )*
    }
}

impl Struct {
    fn print(&self) {
        fields! { print_fields: $name =>
            $(
                println!("{}: {}", stringify!($name), self.$name);
            )*
        }
    }
}

fn main() {
    let s = Struct {
        one: 1,
        two: 2,
        three: 3,
    };
    s.print();
}

Rust Playground

one small downside being however, that this cannot be used in expressions. Unless you add a block around the RHS of fields to bundle up the helper macro and expression in a block expression; but then it’s no longer usable for top-level items. And of course, you couldn’t define a helper macro once, and then combine it with many of the ($macro:ident) => { $macro! {one two three} };-style ones, e.g. if you have multiple such lists of fields and want to handle them all the same. Well… such use-cases could always fall back to manual usage of with_fields above though, anyways…

Speaking of further abstraction and defining-macros-using-macros, of course there are almost no layers of abstraction that cannot be reached.

3 Likes

I tried removing the redundant with_name parameter from your last playground and I see that you added it for a reason as I am getting the errors below. I suppose this is an actual limitation, but I don't understand why the compiler wouldn't be able to do this. Are macros global? Can't they be defined locally?

error[E0659]: `with_name` is ambiguous
  --> src/main.rs:16:17
   |
16 |                   with_name! { $dollar helper }
   |                   ^^^^^^^^^ ambiguous name
...
48 | / fields2! { define_fields2: $name =>
49 | |     struct Struct2 {
50 | |         $(
51 | |             pub $name: String,
52 | |         )*
53 | |     }
54 | | }
   | |_- in this macro invocation
   |
   = note: ambiguous because of a conflict between a macro-expanded name and a less macro-expanded name from outer scope during import or macro resolution
note: `with_name` could refer to the macro defined here
  --> src/main.rs:3:9
   |
3  | /         macro_rules! with_name {
4  | |             ($dollar macro:ident) => {
5  | |                 $dollar macro! { $($i)* }
6  | |             };
7  | |         }
   | |_________^

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.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.