Search code examples
rustenumsmacrosvariant

A macro registering marked structures into an enum


I am searching for a macro, or a procedural macro, which could register some marked structures (or enums) as variants of an enum. Moreover, I would like, that if the marked structures are implementing a trait, then the trait would be implemented by this enum. For example, I would like something like that:

trait MyTrait {
    fn print_it(&self);
}

#[register(RegEnum,MyTrait)]
struct Foo;

#[register(RegEnum,MyTrait)]
struct Bar;

impl MyTrait for Foo {
    fn print_it(&self) { println!("Foo"); }
}

impl MyTrait for Bar {
    fn print_it(&self) { println!("Bar"); }
}

fn main() {
    let reg_1 = RegEnum::Foo;
    let reg_2 = RegEnum::Bar;

    // This will print "Foo"
    reg_1.print_it();

    // This will print "Bar"
    reg_2.print_it();
}

Notice that the macro should build the enum, by considering which structures are marked. The enum would not be defined by the programmer.

Is there a way to do that?


Solution

  • First, you have no way of knowing how much structs are annotated with the attribute. So you have to pass this number for each invocation.

    The macro should cache the struct, and when that number is reached, it'll build and emit the trait. It should also error if the number is already reached, so you'll get future-proof against new structs instead of silent miscompilation.

    Take a look in enum_dispatch's source for an example of a macro that does that.

    Note that order of items is not guaranteed, and moreover, the macro can be called with only part of the items with incremental compilation. Also, as far as I am aware, there is no guarantee that the compiler will use the same process for each invocation, so theoretically it may break, but practically it works well (not sure whether it still works with incremental compilation, though).