4
votes

I'm trying to learn the Rust macro system by writing a simple macro that generates a struct based on some unsigned integer type (u8, u16, u32, u64). I want something like this:

bitmessage! {
    struct Header(u16);
    version: 8, 5; // the first number is the length, second is value
    data: 8, 5;
}

To be more specific, I'm looking for some way to store certain information in an unsigned integer type with various offsets. One use case is to read some bytes and construct some kind of "message":

[ 15 14 13 12 11 10 09 08 | 07 06 05 04 03 02 01 01 ]

The higher part of the message contains some data/information, the lower part a versioning field. (This is just a toy example).

This is my effort so far, but the inner repeating expansion does not compile:

macro_rules! bitmessage {
(struct $name:ident($n:ty); 
    $($field_name:ident: $length:expr, $value:expr;)*)  => {
         struct $name ($n);
         $($name.1 = $name.1 | $value << $length)*
    };
}

One solution could be to store the relevant bytes in a struct, implementing it directly (or with a trait) to get the appropriate fields, but this would involve too much bit-shifting logic (no problem with that, but there must be a more convenient way).

I am aware of bitflags and bitfield. Neither of them matches my use case.

1

1 Answers

3
votes

Declarative macros (macro_rules)

You cannot evaluate expressions in a declarative macro. Declarative macros only create, remove, or move around parts of the input code's abstract syntax tree (AST). No evaluation occurs during macro expansion (even the name "expansion" is a hint).

The best you can do is create code that can be evaluated at compile time, after the macros have been expanded. The subset of code that is valid at compile time is limited, but it will grow in the future.

Procedural macros

Procedural macros are more complicated but more powerful. These are implemented as arbitrary Rust code and they can parse arbitrary Rust code, outputting more arbitrary Rust code.

However, there's no ability to reuse the normal ways of evaluating Rust code. You will have to accept literal values and do all the computation yourself.

Your specific example

It's unclear what you want the result of the macro to be. Remember that macros don't have the ability to "make up" new Rust concepts, they only allow you to express existing repeated concepts with fewer characters.

Because of this, I always recommend that people write out the first two repeated cases fully. This forces you to write the full valid Rust code and identify the differences between them. You can then extract the commonality using any normal Rust technique.

See also: