This works, but test_macro
accepts only one argument:
macro_rules! test_define (
($name:ident) => (
macro_rules! $name (
( $x:expr ) => (
// something
)
);
)
);
test_define!(test_macro);
If I try and do this:
macro_rules! test_define2 (
($name:ident) => (
macro_rules! $name (
( $($x:expr),* ) => (
// something
)
);
)
);
test_define2!(test_macro2);
Compilation fails with:
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> src/main.rs:4:16
|
4 | ( $($x:expr),* ) => (
| ^^^^^^^^^
It is a known bug that nested macros don't allow repetitions in binding patterns (issue #35853).
Unfortunately, there is no workaround. The only solution is to change your API to not rely on repetition inside nested macros.
While it is not possible to do this directly, as kennytm described in this answer, you could do this using procedural macros(in case you really need to, otherwise I would not recommend it).
While it's probably a lot simpler when using nightly
, it is also possible in stable
.
Please tell me in case I missed anything or in case there might be problems when using custom derive like this in the future.
So let's just copy the example from here, and just look at this part of the code:
fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
let name = &ast.ident;
quote! {
impl HelloWorld for #name {
fn hello_world() {
println!("Hello, World! My name is {}", stringify!(#name));
}
}
}
}
inside of the quote!
macro, you are not limited to the implementation of the struct
. you could change this to
quote! {
macro_rules! #name {
($($expr:expr),*) => {
// something
}
}
}
now you have a macro with the same name as a struct
which takes an infinite amount of arguments.
To do this in another macro, the outer macro just has to look like this:
macro_rules! test_define {
($name:ident) => {
#[allow(non_camel_case_types)] // because macro names are lower case
#[allow(dead_code)] // because this struct should never be used
#[derive(HelloWorld)]
struct $name { }
}
};
And now you can call test_define
and then the inner macro:
test_define!(foo);
fn main() {
foo!()
}
However, there is still one problem: people could accidentally access your struct. So there are ways to circumvent this(each solution is a directly linked the problem with the same number):
name the struct in a way which prevents accidental access by pure chance:
macro_rules! test_define {
($name:ident) => {
#[allow(dead_code)]
#[derive(HelloWorld)]
struct Dgfggsdfgujksdsdsdfsdfsdg {
$name: u8,
}
}
};
You have to change your proc macro to use the struct field
instead of name
inside of quote!
and in case the test_define!
is called more than once in the same crate you have 2 structs with identical names which causes a compile time error.
to prevent two identical struct names you could also change test_define!
to take an additional argument:
macro_rules! test_define {
($name:ident, $rep_guard:ident) => {
#[allow(non_camel_case_types)]
#[allow(dead_code]
#[derive(HelloWorld)]
struct $rep_guard {
$name: u8,
}
}
};
You are using a struct field
instead of the name
with this method. To use you now have to write test_define!(foo,fvgfdgdfgdfgd)
, which is really awkward, so I would not recommend this.
This is probably the best option, now you can keep your strange struct
name from solution 1 and just put the whole thing in a module
. Meaning that no one can accidentally access the created struct and you can have an infinite amount of calls to test_define!
.
macro_rules! test_define {
($name:ident) => {
#[macro_use] // to use the macro in the current module
mod $name {
#[allow(dead_code)]
#[derive(HelloWorld)]
struct Dgfggsdfgujksdsdsdfsdfsdg {
$name: u8,
}
}
}
};
The compiler should simply remove all of those structs, as they are dead_code
(at least when building with the --release
flag). You could adapt the quote!
by adding #[macro_export]
if you need it.
Another advantage is that proc macros use your source code in the same way as a String
or as Tokens
which can be cast to a String
, this means that you could create multiple macros, for example:
test_derive!(foo)
=> foo!()
, foo_with_var!(75)
In case there is anything you don't understand, just ask.
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