What is equivalent to #define A(name,size,type) type name; in Rust?

I search with the keywords like rust code substitution, but no result that close to my questions.

I have a C code below

typedef struct {
    #define Operate( name, size, type ) type name;
    #include "def/data/mystruct.def"
} MyStruct;

where mystruct.def content is

Operate( class, 1, uint8_t )
Operate( accuracy, 1, unsigned char )
Operate( offsetScaled, 2, uint16_t)

How do I achieve the same effect in Rust? Thanks.

in C #define creates a macro.
in rust to create a macro one uses macro_rules!
that being said macros in C and rust work quite differently so i don't believe you could do it the exact same way. rust requires its macros to output complete items of valid rust syntax.
so a macro could return a struct definition, a function definition, an expression, a type, but not a single field definition.

1 Like

You'd need something like with_builtin_macros::builtin_macros::include_from_root - Rust I think.

Give the file contents as an input to some macro that parses the file contents and outputs the entire struct definition.

1 Like

how is include_from_root supposed to help exactly ? does it run before macro expansion ?

you can very simply do

macro_rules! my_struct_def {
    ($(Operate($n:ident, $s:literal, $t:ty))*) => {
        struct MyStruct {
            $($n : $t),*
        }

        $(const _ : () = ::core::assert!(::core::mem::size_of::<$t>() == $s);)*
    }
}

my_struct_def! {
    Operate(a, 1, u8)
    Operate(b, 2, i16)
    Operate(c, 24, String)
}

but you can't use include! because it would run after my_struct_def! unless my_struct_def! was in the mystruct.def file

3 Likes

OP seemingly wanted the Operate lines to be in a separate file. Combining include_from_root with the macro you wrote will do that.

with_builtin!(let $this_file = include_from_root!("def/data/mystruct.def") in {
    my_struct_def!($this_file);
});
4 Likes

i see now. the doc was a bit confusing, but after looking at what with_builtin!, it does do what i said it does which is mess with the order of the macro evaluations

1 Like

:upside_down_face: To be fair I would rather write build.rs to generated the code at this point.

3 Likes
macro_rules! my_struct_def {
    ($t_name:ident : $(Operate($n:ident, $s:literal, $t:ty))*) => {
        struct $t_name {
            $($n : $t),*
        }

        $(const _ : () = ::core::assert!(::core::mem::size_of::<$t>() == $s);)*
    }
}

macro_rules! include_struct {
    ($t_name:ident, $path:literal) => {
        ::with_builtin_macros::with_builtin!(let $this_file = include_from_root!($path) in {
            my_struct_def!($t_name : $this_file);
        });
    }
}

include_struct!(MyStruct, "def/data/mystruct.def");

here is what i think is a nice easy to reuse system which does require the with_builtin_macros - Rust dependency.
ofc the Operate() system is quite ugly, i would recommend just using semicolons as seperation instead, but i wanted to stay as close to the original as possible.

i would prob use semicolons instead, or anything you feel like using.

macro_rules! my_struct_def {
    ($t_name:ident : $($n:ident, $s:literal, $t:ty);* $(;)?) => {
        struct $t_name {
            $($n : $t),*
        }

        $(const _ : () = ::core::assert!(::core::mem::size_of::<$t>() == $s, concat!("field ", stringify!($n), " of struct ", stringify!($t_name),  " with type ", stringify!($t), ", was incrorretly declared as having size ", $s, "."));)*
    }
}

macro_rules! include_struct {
    ($t_name:ident, $path:literal) => {
        ::with_builtin_macros::with_builtin!(let $this_file = include_from_root!($path) in {
            my_struct_def!{$t_name : $this_file}
        });
    }
}   

include_struct!(MyStruct, "src/types.txt");
1 Like

@theemathas @Morgane55440

I like the discussion. From there I learned several new lessons about Rust like macro_rules!, include_from_root, though it took me a while to understand how to get work. Appreciate it!

One more question. If I want to achieve similar effect in a more Rust way, what would be recommended? It occurs to me that Rust community may have more simple or concise approach than something like 1 to 1/ C to Rust translation.

Thanks again for the advice.

1 Like

Is it something like in build.rs

fn main() {
    // Create a path that stores the content to be generated
    let out_dir = env::var_os("OUT_DIR").unwrap();
    let dest_path = Path::new(&out_dir).join("mystruct.rs");

    // Write the generated content to the file
    fs::write(
        &dest_path,
        "pub fn mystruct() -> &'static ...,
    ).unwrap();
}

Then in src/main.rs acquire the function and execute it

include!(concat!(env!("OUT_DIR"), "/mystruct.rs"));

fn main() {
    println!("mystruct: {:?}", mystruct())
}

Something like that, yes. Except it would be a struct, not fn.

1 Like

well it is a bit unclear to me what exactly you mean to achieve, why not simply declare the struct directly ?

if you need to respect the Operate($ident, $size, $type) for interoperability, i think the include_struct! macro is fine. a build script is also completely reasonable although i personally prefer sticking to simple declarative macros if possible.

ultimaely if you want to declare the strucure of a type in a custom format in an external file, you will need either

  • a build script
  • something like include_from_root
  • or for said file to actually be valid rust, for exampl with a macro. which is kinda what you do in C. your mystruct.def is kinda "valid" C because the Operate are macro calls. to do this in rust you would need mystruct.defto be one macro call. like
my_struct_def! { MyStruct :
a, 4, u32;
b, 2, i16;
c, 24, String;
d, 9, [u8;9];
}

but at that point why not declare the struct in rust terms.

thhere may be other ideas i am not thinking of

1 Like

I was wondering the same. Possibly, the idea is to have

    #define Operate( name, size, type ) ...
    #include "def/data/mystruct.def"

at multiple places, with different definitions of Operate that generate whatever code is needed at each place. (That's a macro trick that's sometimes used in C code.)

In Rust, I would probably use one or more traits in this case (maybe one per occurrence of this pattern, but it depends). You can either implement those traits directly for the struct, or via a custom derive implementation if you want to support many different structs. Or if the structs are generated via a macro already, then that macro could also generate the impls along with them.

For example, if you want to implement some kind of compile-time reflection of the struct fields, the corresponding trait could look something like:

pub trait Reflect {
    fn fields() -> &'static [Field];
}

pub struct Field {
    pub name: &'static str,
    pub size: usize,
    ...
}

It's probably easier to give advice if you provide some usage examples, though.

1 Like

The code OP has posted is an example of an X Macro, a somewhat forgotten old metaprogramming technique in C.

It's effectively a compiler-accessible data structure that can be used for code generation. Think derive but somewhat inverted and more ad-hoc. I wrote an explainer a while back that shows how to use X Macros and template headers to implement automatic SoA/AoS conversion. Other things you can do include:

  • Static reflection for structs and enums by xmacro-ing their fields
    • You can also add extra metadata to each field by supplying it as additional macro arguments
  • Generating code to process command-line arguments by xmacro-ing the list of flags
  • Automatically declaring and loading OpenGL function pointers by name (as opposed to using something like GLAD)
  • Writing a thread-safe strerror (or equivalent) by xmacro-ing the error codes
2 Likes

Thank you for your advice. Those are helpful! As @Morgane55440 mentions, I will just declare a struct for now. Simpler, the better, at least at this stage to me. Thank you again for all inputs.