Macro plans - syntax
I would like to improve the syntax for both macros by example and procedural macros.
For macros by example, there is a soft backwards compatibility constraint: although new macros do not have to be backwards compatible with old macros, they should be as close as possible to make the transition as easy as possible for macro authors. Ideally, if a macro does not violate hygiene via the current bugs, then it should be usable as a new macro with no changes.
Currently, syntax extensions have no special syntax (they are just Rust code). Although there are attributes for importing macros, declaring macros is fully programmatic. I would like to introduce some syntax for making declaration easier.
Macros by example
We first need to declare macros using a new name, I propose macro
. There is then the question of whether we should continue to treat macro definitions as macro uses of a special macro, or to treat them like any other language item. I prefer the latter, and thus propose macro
rather than macro!
. We also need to handle privacy for macros, and I propose pub macro
for this, in line with other items. Although I don't propose doing so now, we might later also want unsafe macro
.
I would like to make the semi-colons between pattern arms optional (if the user uses braces for the right-hand side). We could consider semi-colons deprecated and disallow them before stabilisation.
I would like to make the kinds of brackets usable in macros stricter. I propose that the body of a macro and the right-hand side of macros must always use braces, and the semi-colon terminated form of macros is no longer allowed. We will continue to accept any kind of bracket (()
, []
, {}
) around the pattern, but the kind of bracket must match the use. Whilst new macros are being stabilised, these changes should cause deprecation warnings rather than errors to make adoption of the new macro system easier.
Example, old macros:
macro_rules! foo (
(bar) => { ... };
($x: ident) => ( ... );
);
new macros:
pub macro foo {
(bar) => { ... }
($x: ident) => { ... }
}
I would also like to make it easier to write macros with a single pattern. These are common and often small, so the boilerplate of writing the current pattern matching style is annoying.
For single-pattern macros I propose a more function-like style, eliding the outermost braces and =>
. For example,
macro_rules! foo (
($x: ident) => ( ... )
);
could be written as
pub macro foo($x: ident) {
...
}
Finally, I propose that declaration of macros is only allowed in item position.
Macro use stays unchanged.
Parsing
macro
is a keyword, so I believe that parsing macros as items should not present any major difficulty. The only slight niggle is distinguishing the two forms, in particular when the pattern is pathological:
macro foo {() => { ... }}
vs
macro foo {() => {}} { ... }
To parse, after finding the macro
keyword and a name, we skip a token tree and look at the next token, if it is a {
, then we must be parsing the simple form and the skipped token tree is the pattern. Otherwise, we are parsing the complex form, and the skipped token tree is the body of the macro.
Alternatives
Keep macro defintion as a macro use using macro!
. This has the 'advantage' of allowing macros to be defined in non-item position. I don't think there is a great use case for this. The other motivation for this is that macros by example should be implementable as a procedural macro. Whilst this is a noble goal, I don't think it actually means we should take this path. In particular, we could view macro expansion as involving a desugaring step and then expansion, where everything other than the desugaring step could be implemented as a procedural macro. Thus, we have the proof that procedural macros are awesome (and an alternative macro system could be implemented as a procedural macro), but the standard macro system has some syntactic advantages (as one would expect for an integral part of the language). I'm not actually proposing implementing macros using such a desugaring to procedural macros, but we could if there is demand.
Replace the $x
syntax for arguments. The dollar-signed variables are ugly, make macros look unlike other Rust code, and have little precedent from other languages. However, changing this part of the syntax would have backwards compatibility issues. Furthermore, we cannot use just x
(if we want to continue to allow pattern-matching) since we then couldn't distinguish between variables and literals in patterns. So, it is not obvious that there is a better syntax.
It has been suggested that we could have macros without patterns, e.g, let x = foo!;
rather than let x = foo()!;
. This would have some readability advantages and shouldn't complicate parsing. It does however, add complexity and I am not sure if the gains are worth it (in particular, we don't support any equivalent for functions).
Include the !
in the name of the macro, e.g., pub macro foo!() { ... }
. Not sure how I feel about this, this sort of follows the precedent of lifetimes where we use '
in both the declaration and use. The backwards compatibility issue seems minor here. I worry that when parsing (or reading), it is hard to distinguish a macro use from a macro declaration (especially if we allow macros in identifier positions).
Finally, we might consider a more dramatic change to the syntax, which might perhaps fix the $x
problem mentioned above. However, this would have big backwards compatibility concerns, the new problems we introduce might not be better than the old problems we fix, and it would be a lot of work to design and implement for relatively little gain.
Procedural macros
I propose that procedural macros are Rust functions marked with the attribute #[macro]
or #[macro_attribute]
. The former gives a macro used with function-like syntax with the name of the function, the latter a macro used with attribute-like syntax. #[cfg(macro)]
indicates that an item should be compiled in the plugin phase only. For example,
#[cfg(macro)]
pub mod macros {
#[macro]
pub fn foo(...) -> ... { ... }
#[macro_attribute]
pub fn bar(...) -> ... { ... }
}
#[::macros::bar]
fn main() {
::macros::foo!();
}
Initially, only an entire crate may be marked as #[cfg(macro)]
(i.e., the above example would not be legal). I hope we can ease this restriction later.
I'll save the details of the arguments that a procedural macro function takes and of naming macros for later.
Alternatives
We could have a syntax closer to that for macros by example. However, we would still need to pass some context to the macro and distinguish the macro as a procedural one (i.e., whether the code should be executed or copied when expanded). I believe that using proper functions is therefore nicer. We could use the macro
keyword, rather than an attribute to mark the function as a macro (thus making them a little more similar to macros by example). However, we would then need some other way of marking attribute-like macros.
We could implement a trait rather than using an attribute (as we currently do). I believe using an attribute is more appropriate here - it is more syntactic than a trait and macros are part of the syntactic world. This also means that the parser can distinguish macros from other functions, thus allowing them to be mixed in the same crate if desired.