RFC #3086 defines a metafunction syntax for declarative macros, e.g. ${ignore(...)}. As part of determining exactly what the "arguments" should look like, I've been tasked with collecting a list of currently proposed metafunctionality (included below). I'd also like to ask everyone else: what other kinds of metafunctionality would be useful for writing declarative macros?
Maybe this (metafunctions) can be used to improve the generics situation for declarative macros. There could be a generics fragment and then some metafunction that does something similar to syn::Generics::split_for_impl.
For example, currently its quite easy to accept simple Rust types in declarative macros:
Note that all of these are just proposed ideas, and every one I could find at that. The purpose of collecting this list is specifically to define what we actually need/want out of declarative macro metafunctions (and if we want them at all or should unaccept that part of RFC#3086).
And if you're saying the list itself is just poorly formatted: sorry, I tried my best
The idea of providing some of this functionality is that it will allow more macros which are currently required to use the much more complicated proc macro system to become decl macros instead. So in that way, the idea/hope is that by giving decl macros more power we decrease the required complexity of macros which can then move from being implemented as proc macros to being decl macros instead.
Hmm I do not really like the assign operation, instead I would like to see more classic control flow operators on meta variables. When I first learned macros, I asked myself why they were not implemented using those and I have not found a deal breaking problem with them.
So why do we not just allow something like (example from the assign zulip link):
There might even be some more syntax to also add indexing and other goodies.
I could also imagine the use of other control flow items such as match, if etc.
To my mind, this work is a result of Rust still firmly stuck in the sunken cost fallacy. We are still trying to add on more workarounds on top of an unfit design.
The current macro_rules system as it currently stands has foreign syntax and semantics and simply isn't fit for purpose, given we all hope Rust would be the language of the next 50 years.
We could do much better by taking inspiration from Nemerle and more recently https://www.circle-lang.org/, projects that have much better thought out macro systems.
E.g instead of trying to force a scheme like recursive macro definition and trying to add to it metafunctions to count numbers of repetitions, wouldn't it be way WAY easier to just use plain Rust for loops and iterators?
I feel like there is some middle ground between "We implement C++ templates" and the current macro system.
The macro system has some poor limitations and one is thus required to write a proc-macro. I do not like that, because many people will never want to touch proc-macros and they always are such a hastle to integrate into your projects (add a helper crate etc.). I definitely agree with you that we do not want to create a half baked system, but if we can improve the current marco system in such a way that it is easier to understand and a little bit more powerful then I think we should do that.
For example, why do I need a proc-macro to concatenate idents?
I like the argument destructuring approach where you specify how the input looks like, but expanding the macro itself should be more ergonomic.
iirc in the future macro will be used to declare macros, we could use that opportunity to also define a new macro syntax that would deal with these problems.
much easier to understand than all of the ceremony required to set up an attribute macro.
Declarative macros are also much simpler for IDEs to support (even with more complicated meta functionality) because they're still purely declarative, so the IDE can know that e.g. you're editing in $($body:tt)* above and that editing there won't change the macro expansion. Proc macros are formally impossible to track in such a fashion because they're defined procedurally.
Then there's also the extra bit that proc macros have to be compiled separately from the main crate. This can be improved in the future such that it can be part of the same package, but it will always need to be a separate build-time part rather than just in the normal crate.
I agree with @CAD97 proc-macros are very useful, but I think we can make decl macros work for many cases where we currently need a proc-macro.
What also came to my mind was named matches (I do not know a better name).
So for example:
macro_rules! my_macro {
($p(print)? $a:ident + $b:ident) => {
if ${exists($p)} // how to specify print here?? {
println!("{} + {}", stringify!($a), stringify!($b));
}
$a + $b
};
}
So I would like to assign a name to some fixed construct in the input. A workaround is at the moment to use ident/tt and then later in a helper macro require the concrete construct, but then the error is less readable and the macro always drags around an ident that actually is only ever something constant.
I do not know if this fits here, but I think that we could use better support for inner helper macros. They should not be callable from outside the outer macro and should be easier to declare (maybe another parameter syntax? but this might be a bit too extreme).
The assign one is cute; we'd have to see how it fares: I don't see it as paramount, but I imagine it being quite convenient
concat is so direly needed that it could indeed warrant being special-cased as a special metavariable function.
But the main thing we need, really, is this so-called eager.
An eager expansion operator such as ${macro!(…)} is a Game Changer
Nothing[1] more is needed. That is, rather than trying to come up with dozens and dozens of metavar combinations, we can just define custom ones with macros and eager.
For instance, to concatenate identifiers:
#[test]
fn ${concat_idents!($fname, _test)} ()
to replace ${concat(…)}, as well as ${respan!(…)}, and so on and so forth.
This means that a very important question to sort out is whether this eager mechanism will be featured:
If so, then let's not overcomplicated metavar expressions, and just let them defer to user-defined helper macros (now, the stdlib could consider adding more of those, but then these would just be T-libs question rather than T-lang ones!);
If not, then we'd need T-lang extensions for every new usability need, or just a lack of support as we currently do.
Aside: unrelated, but the "named" capture groups idea, which has already been brough before, in this very thread, and in this forum as a standalone thread, is indeed quite important to get rid of the $(@$($name:tt)?)? hacks that are so pervasive nowadays (in other words, the current status quo in this regard is horrendous). But since named capture groups is yet another whole level of macro modification, I think a way simpler and more elegant solution, which was also suggested in that thread (but not in this one!), is to be able to have :empty capture groups.
EDIT: this term was a bit too adamant, my bad. Obviously there are still meta(var)-based functionality such as index, count, and whatnot which would require dedicated metavar functions, @CAD97, you're completely right . But I suspect there won't be that many of those: the vast majority of use cases ought to be manageable using macro helpers ↩︎
That's not quite true: index and length (the RFC-accepted ones) aren't doable with just macros, as they need access to compiler context. assign is just a cute assistant, but also isn't possible without compiler support. But I think you're correct on the rest.