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?
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.
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.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With