Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building an enum inside a macro

Tags:

macros

rust

Is it possible to build an enum inside a Rust macro using fields that are defined as macro parameters? I've tried this:

macro_rules! build {
    ($($case:ty),*) => { enum Test { $($case),* } };
}

fn main() {
    build!{ Foo(i32), Bar(i32, i32) };
}

But it fails with error: expected ident, found 'Foo(i32)'

Note that if the fields are defined inside the enum, there is no problem:

macro_rules! build {
    ($($case:ty),*) => { enum Test { Foo(i32), Bar(i32, i32) } };
}

fn main() {
    build!{ Foo(i32), Bar(i32, i32) };
}

It also works if my macro only accepts simple fields:

macro_rules! build {
    ($($case:ident),*) => { enum Test { $($case),* } };
}

fn main() {
    build!{ Foo, Bar };
}

But I've been unable to get it to work in the general case.

like image 419
Jmb Avatar asked Dec 25 '22 06:12

Jmb


1 Answers

It's absolutely possible, but you're conflating totally unrelated concepts.

Something like $case:ty does not mean $case is something which looks like a type, it means $case is literally a type. Enums are not made up of a sequence of types; they're made up of a sequence of variants which are an identifier followed (optionally) by a tuple structure body, a record structure body, or a tag value.

The parser doesn't care if the type you give it happens to coincidentally look like a valid variant, it's simply not expecting a type, and will refuse to parse one in that position.

What you need is to use something like $case:variant. Unfortunately for you, no such matcher exists. The only way to do something like this is to manually parse it using a recursive incremental parser and that is so out of scope of an SO question it's not funny. If you want to learn more, try the chapter on incremental TT munchers in the Little Book of Rust Macros as a starting point.

However, you don't appear to actually do anything with the cases. You're just blindly substituting them. In that case, you can just cheat and not bother with trying to match anything coherent:

macro_rules! build {
    ($($body:tt)*) => {
        as_item! {
            enum Test { $($body)* }
        }
    };
}

macro_rules! as_item {
    ($i:item) => { $i };
}

fn main() {
    build!{ Foo, Bar };
}

(Incidentally, that as_item! thing is explained in the section on AST coercion (a.k.a. "the reparse trick").)

This just grabs everything provided as input to build!, and shoves it into the body of an enum without caring what it looks like.

If you were trying to do something meaningful with the variants, well, you're going to have to be more specific about what you're actually trying to accomplish, as the best advice of how to proceed varies wildly depending on the answer.

like image 112
DK. Avatar answered Dec 31 '22 02:12

DK.