I'm trying to create a macro that generates a struct
that provides a set of methods that are passed into the macro. For example, calling:
create_impl!(StructName, fn foo() -> u32 { return 432 })
should generate an empty struct StructName
that provides the method foo()
.
My initial attempt at this uses the item
macro arg type. However, when I try and use an item
in the rule, I get the following compiler error:
error: expected one of `const`, `default`, `extern`, `fn`, `pub`, `type`, `unsafe`, or `}`, found `fn foo() -> u32 { return 42; }`
--> src/lib.rs:40:13
|
40 | $($function)*
| ^^^^^^^^^
Is it possible to use item
arguments to define methods in generated structs this way? Is there something I'm missing?
Here's the full macro I've defined:
macro_rules! create_impl {
($struct_name:ident, $($function:item),*) => {
struct $struct_name {
}
impl $struct_name {
// This is the part that fails.
$($function)*
}
};
}
The short answer is "no, you can't use the item
matcher for a method".
According to the reference, items are the top level things in a crate or module, so functions, types, and so on. While a struct
or impl
block is an item, the things inside them aren't. Even though syntactically, a method definition looks identical to a top level function, that doesn't make it an item.
The way Rust's macro system works is that once a fragment has been parsed as an item
, e.g. using $foo:item
, it's then forever an item
; it's split back into tokens for reparsing once the macro is expanded.
The result of this is that $foo:item
can only be in the macro's output in item position, which generally means top-level.
There are a couple of alternatives.
The simplest is to use the good old tt
(token tree) matcher. A token tree is either a non-bracket token or a sequence of tokens surrounded by balanced brackets; so $(foo:tt)*
matches anything. However, that means it will gobble up commas too, so it's easier to just add braces around each item:
macro_rules! create_impl {
($struct_name:ident, $({ $($function:tt)* }),*) => {
struct $struct_name {
}
impl $struct_name {
$($($function)*)*
}
};
}
Then you have to use it with the extra braces:
create_impl!(StructName, { fn foo() -> u32 { return 432 } }, { fn bar() -> u32 { return 765 } });
You can also just match the syntax you want directly, rather than delegating to the item
matcher:
macro_rules! create_impl2 {
($struct_name:ident, $(fn $fname:ident($($arg:tt)*) -> $t:ty $body:block),*) => {
struct $struct_name {
}
impl $struct_name {
$(fn $fname($($arg)*) -> $t $body)*
}
}
}
Of course since it's explicit, that means that if you want to support functions without a return type, you need to add another case to your macro.