3

I'm trying to write a Rust macro that allows me to make use of the field names and types of a struct declaration, but I still need to emit the struct.

I've got it working with optional attributes, visibility of the struct (thanks to The Little Book of Rust Macros), but can't figure out how to deal with the optional presence of pub in the individual fields.

So far I've got:

macro_rules! with_generic {
    ($(#[$struct_meta:meta])*
    pub struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        with_generic![(pub) $(#[$struct_meta])* struct $name {$($fname: $ftype) ,*}];
    };

    ($(#[$struct_meta:meta])*
    struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        with_generic![() $(#[$struct_meta])* struct $name {$($fname: $ftype), *}];
    };

    (
    ($($vis:tt)*)
    $(#[$struct_meta:meta])*
    struct $name:ident { $($fname:ident : $ftype:ty), *}
    ) => {
        // emit the struct here
        $(#[$struct_meta])*
        $($vis)* struct $name {
            $($fname: $ftype,)*
        }

        // I work with fname and ftypes here
    }
}

And it works with something like

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    pub struct Person {
        first_name: String,
        last_name:  String
    }
}

or

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    struct PrivatePerson {
        first_name: String,
        last_name:  String
    }
}

but doesn't work with

with_generic! {
    #[derive(PartialEq, Eq, Debug)]
    struct MixedPerson {
        pub first_name: String,
        last_name:  String
    }
}

I'd like to get some help on how to make the macro work with that last case. I feel like I might be missing something basic here, such as the type used for binding visibility. If there's a way to bind the whole struct tree while getting the field names and types, that would also be fine.

I'd also like to learn how to get it to work with structs that have lifetime parameters, but maybe that should be a separate question.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lloydmeta
  • 1,289
  • 1
  • 15
  • 25

3 Answers3

2

You can't. At least, not with a single, non-recursive rule. This is because Rust doesn't have a macro matcher for visibility.

The parse-macros crate contains a parse_struct! macro that shows the work necessary to completely parse a struct definition. Short version: you need to parse each field individually, with one rule for each of "has pub" and "doesn't have pub".

I'd also just note that there's another case your macro doesn't yet account for: attributes on the fields, which is needed for doc-comments on them to work.

Quite soon, macros 1.1 should be stabilised, which might provide an easier approach (assuming you can express your macro as a derivation, and don't care about older versions of Rust).

DK.
  • 55,277
  • 5
  • 189
  • 162
  • "I'd also just note that there's another case your macro doesn't yet account for: attributes on the fields, which is needed for doc-comments on them to work" Ugh, thanks I knew I forgot something. Looking at your code (btw...amazing stuff), I now realise I forgot a *lot* of other stuff, like where clauses. You're right though, I essentially want a custom derive: Is there somewhere I can read more about how macros 1.1 helps there? – lloydmeta Feb 02 '17 at 14:03
  • @lloydmeta At this point, not that I know of. I'd just go poke at the source for something like `serde-derive`. – DK. Feb 02 '17 at 14:05
  • Thanks @Shepmaster. I also found this https://cbreeden.github.io/Macros11/ – lloydmeta Feb 02 '17 at 15:58
2

Since Rust 1.30, you can match visibility keywords with the vis specifier. A vis metavariable will match nothing if there is no visibility keyword to match, so you don't even need to use it inside $()*. This change makes with_generic vastly simpler:

macro_rules! with_generic {
    ($(#[$struct_meta:meta])*
    $sv:vis struct $name:ident { $($fv:vis $fname:ident : $ftype:ty), *}
    ) => {
        // emit the struct here
        $(#[$struct_meta])*
        $sv struct $name {
            $($fv $fname: $ftype,)*
        }
        // do whatever else you need here
    }
}
trent
  • 25,033
  • 7
  • 51
  • 90
1

Rust 1.15 was officially released shortly after I asked this question and brings procedural macros (custom derive) support like @DK. has said.

Going forward, I think custom derives w/ syn and quote will be the standard way of doing this kind of thing and side-steps this issue completely since you no longer need to manually re-emit the struct.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
lloydmeta
  • 1,289
  • 1
  • 15
  • 25