I am using procedural macros for more than one year now and will use these for other projects. As far as I know there is not much information on procedural macros and even less on how it will evolve. I know that even Rust-core developers have little idea of the future but since I am using this feature for my research work, I would like to discuss about what we should expect of it.
(1) If I understand it well, a procedural macro is expanded before any analysis or transformation of the surrounding code, is that right? It implies that code inside the procedural macro can not query information on Rust code declared outside this macro.
Let me demonstrate that with a project I work on: a parser generator. Currently, semantics actions are Rust functions declared inside the procedural macro; I actually need the return type of these functions. Therefore, my first question is:
- Given a function name, can I access its type signature even if it is declared outside the procedural macro?
Of course I can generalize this question to any named-items. If my statement in (1) is true, then it should not be easily possible.
Now, for another project, I have another problem. I try to design a constraint modelling language as an EDSL within Rust. It could be do-able if I defined an entire new language completely independant of Rust, however what I want to do is to use Rust constructions such as loops or alternatives inside this language so I do not have to re-implement these. Consider the following example:
let mut space = FDSpace::default();
let mut queens = vec![];
// ...
tell! space {
queens[i] + i != queens[j] + j
};
Right now, from inside the macro tell
there is no way to choose the correct constraint since we do not know the type of the variables. Actually, this is not that bad because I compile these constraints into generic structures and picking the right implementation is done by Rust later. However, it is a big problem for optimizations: we could imagine the macro to rewrite this constraint into a more efficient version if we are sure we are manipulating integers. Of course, it could probably be done at the Rust level through trait overloading and trait specialization. However, coming from the C++ world, I really know that using genericity for meta-programming leads to code difficult to write, read and maintain… This is why I believe that syntax extension are a smart replacement of “hard” meta-programming. My second question is:
- In addition of the syntax tree, could we imagine a list of the currently accessible (local) variables along with their types?
It is possible that types have not been infered yet when we expand the macro. There are several possible strategies for this, but I think the simplest is to give the type _
to the variable (as it is done now) and to let the macro cope with it. After macro expansion, Rust inference will give the correct type to that variable. It would required an “expansion on-demand” mechanism, I think that expansion is currently done before (nearly) everything else.
Maybe that what I described is a kind of procedural macro working on MIR instead of working on HIR?
If I missed RFCs or interesting documentation/discussion about procedural macros, please let me know.