简体   繁体   中英

How do I create a parameterised type from a macro?

I have a macro that creates a struct and a heap of supporting functions and trait implementations. The interesting bit for this question is:

macro_rules! make_struct {
    ($name: ident) => {
        struct $name;
    }
}

This works as you'd expect:

make_struct!(MyStruct);

If I want to make a parameterised type however, I'm out of luck:

make_struct!(AnotherStruct<T: SomeTrait>);

test.rs:8:27: 8:28 error: no rules expected the token `<`
test.rs:8 make_struct!(AnotherStruct<T: SomeTrait>);

The name of the struct is an ident so I can't just change that in the macro args (eg to ty ):

test.rs:3:16: 3:21 error: expected ident, found `MyStruct`
test.rs:3         struct $name;

So how do I write this macro to be able to handle both? Or do I need to separate ones? In the latter case, what does the macro look like?

After the keyword struct , the parser expects a ident token tree, which may be followed by < and more; ty is definitely not what it wants (as an example of why it wouldn't work, (Trait + Send + 'static) is a valid ty, but struct (Trait + Send + 'static); clearly doesn't make sense).

To support generics, you will need to produce more rules.

macro_rules! make_struct {
    ($name:ident) => {
        struct $name;
    };
    ($name:ident<$($t:ident: $constraint:ident),+>) => {
        struct $name<$($t: $constraint),+>;
    }
}

As you doubtless observe, making it support everything that the parser would accept in that location is nigh impossible; macro_rules isn't that clever. There is, however, this one weird trick (static code analysers hate it!) which allows you to take a sequence of token trees and treat it as a normal struct definition. It uses just a little bit more of indirection with macro_rules:

macro_rules! item {
    ($item:item) => ($item);
}
macro_rules! make_struct {
    ($name:ident) => {
        struct $name;
    };
    ($name:ident<$($tt:tt)*) => {
        item!(struct $name<$($tt)*;);
    };
}

Note that due to overzealousness, ident doesn't currently allow sequence repetition ( $(…) ) to immediately follow it, so you'll be stuck putting some token tree between the ident and the repetition, eg $name:ident, $($tt:tt)* (yielding AnotherStruct, <T: SomeTrait> ) or $name:ident<$(tt:tt)* => struct $name<$($tt)*; . The value of this is reduced by the fact that you can't pull it apart quite so easily to get at the individual generic types, which you will need to do for things like inserting PhantomData markers.

You may find it helpful to passing the entire struct item; it passes as the item type (just like a fn , enum , use , trait , &c. would do).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM