I like the simple but forwards-compatible design which has been put forth and accepted in "macros 1.1" in order to support custom derive macros in a stabilize-able way, but I also want to be able to use the same tools in order to implement procedural foo!
style macros. This is a pre-RFC for a (I think) small addition to "macros 1.1" which adds support for these "bang" macros.
I'd love feedback as to why this is a bad idea, or ideas for improvements. I don't plan to propose this as a PR or expect that this will be accepted until at least after unstable "macros 1.1" support lands in rustc, but that seems to be happening soon, as there is already a PR open with a basic implementation.
Summary
Extend the subset of "macros 2.0" (A.K.A. "macros 1.1") defined in rfc #1681 to
also support traditional-style foo!
macros, while maintaining a small enough
additional surface area to ensure that it does not pose a future maintenance
burden on the compiler.
Motivation
Currently, traditional foo!
style macros are supported through the
macro_rules!
system. This uses a pattern matching style to allow defining
custom macros in user defined code. Unfortunately, some types of macros are
impossible to support with this style, as they require procedural code running
capabilities. In addition, many complex macros can easily cause the macro
recursion limit to be exceeded while parsing, and slow down compilation time,
due to their complexity.
As with "macros 1.1", this RFC does not aim to architecturally improve on the
current procedural macro system. Namely, this system will not support hygiene.
The goal with this RFC is to allow for parallel experimentation with custom
derive and procedural macros built on top of the TokenStream
system which was
originally proposed in "macros 1.1", and will be extended over time to bring it
closer to "macros 2.0".
This feature would likely be stabilized separately from custom derive, as the
need for it in the ecosystem is less serious, however, it will provide a
platform for experimentation, and ensure that the TokenStream
APIs which are
developed also work well for procedural macro writers.
A stabilized version of procedural macros, even one as limited as this one,
would enable new types of macros which wouldn't have been possible to be defined
in stable userspace before. For example, a user library could add utf16!()
to
define UTF-16 string literals (which are useful for efficient C code interop
with existing code, such as gecko, which uses UTF-16 extensively rather than
UTF-8), or define new macros like format_args!()
and regex!()
which parse
string arguments, and use them to generate code.
Detailed design
Like custom derive macros defined in "macros 1.1", these procedural macros will be defined as functions which have the signature:
fn(TokenStream) -> TokenStream
and are annotated with the attribute #[rustc_macro_bang(foo)]
. For example,
the following is an implementation of a procedural macro definition for foo!
:
#![crate_type = "rustc-macro"]
#![crate_name = "foo"]
extern crate rustc_macro;
use rustc_macro::TokenStream;
#[rustc_macro_bang(foo)]
pub fn foo(input: TokenStream) -> TokenStream {
let source = input.to_string();
// Parse `source`, and build up new source code which should replace the
// macro in the resulting program.
let source = foo_impl(&source);
// Parse this back to a token stream and return it
source.parse().unwrap()
}
Like in "macros 1.0" this attribute may only occur within "rustc-macro" crates, and
can be imported from the "rustc-macro" crate with #[macro_use]
.
It can be used within a consumer crate as follows:
#[macro_use]
extern crate foo;
// ...
foo!(...);
foo![...];
foo!{...}
The TokenStream
which is passed to the #[rustc_macro_bang(foo)]
function may
or may not contain the enclosing []
, {}
, or ()
of the invocation site
(See Unresolved Questions).
Drawbacks
This adds a small amount of complexity to this temporary "macros 1.1" system. Depending on how far off this system is from the future "macros 2.0", this may be undesirable. If this feature is stabilized, it will mean that it must be supported into the future.
This macro system also doesn't support hygiene or other features which we would
hopefully want to be able to support in macros in the future. Unlike custom
derive macros, foo!
-style macros often define variables which could
potentially conflict with other names in the environment. Macro writers using
this system will have to be careful not to create conflicts.
Alternatives
The main alternative to implementing this or something like it which exposes the
procedural TokenStream
transformer for foo!
style macros, as well as custom
derive, is to not implement it. This has the disadvantage of not providing any
of the advantages explained in the motivation section, but will not add extra
complexity to our existing macro system.
Unresolved questions
-
Should the
TokenStream
which is passed to the macro contain the()
,[]
, or{}
which enclose the parameters to the macro? If it does not, then the macro will be unable to distinguish between the different formats, like an existingmacro_rules!
macro. Is this desirable? -
All of the unresolved questions related to "macros 1.1" also apply to this RFC. The answers which would be used for "macros 1.1" will likely also apply to this system unless there is a good reason to act otherwise.
PS. This is one of my first RFCs - so I probably messed some stuff up. I'd love feedback