Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 867
Rob N Avatar asked Jun 02 '15 06:06

Rob N


People also ask

How do you pass parameters to a macro?

A parameter can be either a simple string or a quoted string. It can be passed by using the standard method of putting variables into shared and profile pools (use VPUT in dialogs and VGET in initial macros). This method is best suited to parameters passed from one dialog to another, as in an edit macro.

What are macro parameters?

Macro parameters enable you to pass values into the macro at macro invocation, and set default values for macro variables within the macro definition. In this blog post, I also discuss how you can pass in a varying number of parameter values. There are two types of macro parameters: positional and keyword.


1 Answers

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, e.g. $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).

like image 56
Chris Morgan Avatar answered Jan 05 '23 02:01

Chris Morgan