-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Macro reform #453
RFC: Macro reform #453
Changes from 1 commit
33f9f6d
4b5e09f
79c74f9
193e2ab
82f7c16
3ad1ce3
df4bb87
2ef9932
01563f6
1fc4d1e
f517cd1
fea0c6d
3dd2ee9
6f7ea0c
9bbb13f
ef13bdc
7a25233
0471d61
ee9e2e8
0c10871
ee22f7f
c6cdf8c
45ff6ec
3e5861d
a503fb4
ebc0a85
2f6bdc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
- Start Date: 2014-11-05 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Various enhancements to `macro_rules!` ahead of its standardization in 1.0. | ||
|
||
# Motivation | ||
|
||
`macro_rules!` has [many rough | ||
edges](https://github.com/rust-lang/rfcs/issues/440). A few of the big ones: | ||
|
||
- You can't re-export macros | ||
- Even if you could, names produced by the re-exported macro won't follow the re-export | ||
- You can't use the same macro in-crate and exported, without the "curious inner-module" hack | ||
- There's no namespacing at all | ||
- You can't control which macros are imported from a crate | ||
- You need the feature-gated `#[phase(plugin)]` to import macros | ||
|
||
These issues in particular are things we have a chance of addressing for 1.0. | ||
This RFC contains plans to do so. | ||
|
||
# Detailed design | ||
|
||
## Rename | ||
|
||
Rename `macro_rules!` to `macro!`, analogous to `fn`, `struct`, etc. | ||
|
||
## `#[visible(...)]` | ||
|
||
`#[macro_export]` and `#[macro_escape]` are replaced with a new attribute | ||
applied to a `macro!` invocation: | ||
|
||
* `#[visible(this_crate)]` — this macro "escapes" up the module hierarchy to | ||
the crate root, so it can be used anywhere in the crate after the definition | ||
(according to a depth-first traversal). This is like putting `#[macro_escape]` | ||
on the module and all its ancestors, but applies *only* to the macro with the | ||
`#[visible]` attribute. | ||
|
||
* `#[visible(other_crates)]` — same meaning as `#[macro_export]` today | ||
|
||
These can be combined as `#[visible(this_crate, other_crates)]` (in either | ||
order). | ||
|
||
The default (as today) is that the macro is only visible within the lexical | ||
scope where it is defined. | ||
|
||
## `$crate` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be a minor and incremental change, which is helpful, so +1 to it. I don't think this is something we'd want in the eventual perfect macro world, but seems like a good band aid fix for macro_rules. |
||
|
||
Add a special metavar `$crate` which expands to `::foo` when the macro was | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bikeshed: I would prefer something like |
||
imported from crate `foo`, and to nothing when it was defined in-crate. | ||
`$crate::bar::baz` will be an absolute path either way. | ||
|
||
This feature eliminates the need for the "curious inner-module" and also | ||
enables macro re-export (see below). It is [implemented and | ||
tested](https://github.com/kmcallister/rust/commits/macro-reexport) but needs a | ||
rebase. | ||
|
||
## Crate scope for macros | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm keen for this in general, but I don't like the proposed syntax. I would prefer using attributes for this. |
||
|
||
Instead of a single global namespace for macro definitions, we now have one | ||
namespace per crate. We introduce an attribute to `use` macros with optional | ||
renaming: | ||
|
||
```rust | ||
#[use_macros(std::vec, std::panic as fail)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Post 1.0, this could expand to something like Of course the syntax and expansion semantics are out of scope of this PR - just illustrating a possible migration path. |
||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should introduce language syntax for macros - it is clear at the moment that everything macro-like uses its own syntax which is separate from macro-free Rust. I prefer to keep it that way, it makes macro systems more pluggable and I believe will make things easier to understand. |
||
|
||
(We'd need to extend attribute syntax, or change this to be compatible.) | ||
|
||
This can be applied to a module, a function, or anywhere else attributes are | ||
allowed. | ||
|
||
There's no way to invoke a macro with a qualified name; this obviates the need | ||
for changes to the grammar / parser. | ||
|
||
This isn't an actual `use` item because macro expansion happens before name | ||
resolution, and libsyntax knows nothing about the latter. | ||
|
||
This change applies to `macro!` and to all other syntax extensions, even | ||
decorators that are used as attributes. | ||
|
||
Many macros expand using other "private macros" as an implementation detail. | ||
For example, librustc's `declare_lint!` uses `lint_initializer!`. The client | ||
should not know about this macro, although it still needs to be exported for | ||
cross-crate use. The solution is to allow `use_macros` on `macro!` itself, and | ||
allow `$crate` in that context: | ||
|
||
```rust | ||
/// Not to be imported directly. | ||
#[visible(other_crates)] | ||
macro! lint_initializer ( ... ) | ||
|
||
/// Declare a lint. | ||
#[visible(other_crates)] | ||
#[use_macros($crate::lint_initializer)] | ||
macro! declare_lint ( | ||
($name:ident, $level:ident, $desc:expr) => ( | ||
static $name: &'static $crate::lint::Lint | ||
= &lint_initializer!($name, $level, $desc); | ||
) | ||
) | ||
``` | ||
|
||
The macro `lint_initializer!` will be visible only during further expansion of | ||
the result of invoking `declare_lint!`. | ||
|
||
Procedural macros need their own way to manipulate the expansion context, but | ||
that's an unstable internal API, so it's outside the scope of this RFC. | ||
Ideally the implementation of `macro!` will use the same API. | ||
|
||
## Macro re-export | ||
|
||
With `$crate` we can easily re-export macros that were imported from another | ||
crate. | ||
|
||
For example, libcollections defines a `vec!` macro, which would now look like: | ||
|
||
```rust | ||
#[visible(other_crates)] | ||
macro! vec ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this could expand to |
||
($($e:expr),*) => ({ | ||
let mut _temp = $crate::vec::Vec::new(); | ||
$(_temp.push($e);)* | ||
_temp | ||
}) | ||
) | ||
``` | ||
|
||
Currently, libstd duplicates this macro in its own `macros.rs`. Now it could | ||
do | ||
|
||
```rust | ||
#![reexport_macros(collections::vec)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this expand to something like |
||
``` | ||
|
||
as long as the module `std::vec` is interface-compatible with | ||
`collections::vec`. | ||
|
||
(Actually the current libstd `vec!` is completely different for efficiency, but | ||
it's just an example.) | ||
|
||
## Auto-load macros | ||
|
||
Since macros are now crate-scoped, we can load macros from every `extern crate` | ||
without a special attribute. (Probably we should exclude `extern crate`s that | ||
aren't at the crate root, because there's no way `$crate` paths will be | ||
correct.) | ||
|
||
`#[phase(plugin)]` becomes simply `#[plugin]` and is still feature-gated. It | ||
only controls whether to search for and run a plugin registrar function. The | ||
plugin itself will decide whether it's to be linked at runtime, by calling a | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would a syntax extension want to link itself in at runtime? It injects a runtime dependency on librustc, so most (all?) syntax extensions have a separate crate for runtime support (e.g. regex and regex_macros, or phf and phf_mac). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I don't expect it to be a common situation. One example would be quasiquoting as a library. Basically it's a capability we have today with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My gut says that |
||
`Registry` method. | ||
|
||
In the future I would like to support `#[plugin(... any metas ...)]` where | ||
these "arguments" are available in the registrar and can be used to configure | ||
how the plugin works. This RFC does not cover that feature; I just want to | ||
make sure our design is compatible. | ||
|
||
|
||
# Drawbacks | ||
|
||
This is big churn on a major feature, not long before 1.0. | ||
|
||
We can ship improved versions of `macro!` in a back-compat way (in theory; I | ||
would like to smoke test this idea before 1.0). So we could defer all this | ||
reform until after 1.0. The main reason not to is macro import/export. Right | ||
now every macro you import will be expanded using your local copy of `macro!`, | ||
regardless of what the macro author had in mind. | ||
|
||
# Alternatives | ||
|
||
We could try to implement proper hygienic capture of crate names in macros. | ||
This would be wonderful, but I don't think we can get it done for 1.0. | ||
|
||
We would have to actually parse the macro RHS when it's defined, find all the | ||
paths it wants to emit (somehow), and then turn each crate reference within | ||
such a path into a globally unique thing that will still work when expanded in | ||
another crate. Right now libsyntax is oblivious to librustc's name resolution | ||
rules, and those rules can't be applied until macro expansion is done, because | ||
(for example) a macro can expand to a `use` item. | ||
|
||
nrc suggested dropping the `#![macro_escape]` functionality as part of this | ||
reform. Two ways this could work out: | ||
|
||
- *All* macros are visible throughout the crate. This seems bad; I depend on | ||
module scoping to stay (marginally) sane when working with macros. You can | ||
have private helper macros in two different modules without worrying that | ||
the names will clash. | ||
|
||
- Only macros at the crate root are visible throughout the crate. I'm also | ||
against this because I like keeping `lib.rs` as a declarative description | ||
of crates, modules, etc. without containing any actual code. Forcing the | ||
user's hand as to which file a particular piece of code goes in seems | ||
un-Rusty. | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we are adding syntax for macro import, why not rely on that to import a macro from another module into where you want it, rather than on macro_escape? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds like the difficult "proper name resolution for macros" solution, but I'll think more about it. In the proposal as written all macros live directly under a crate so it's not clear how to extend it to intra-crate import. |
||
# Unresolved questions | ||
|
||
Do we support globs in `use_macros`? How does prelude injection work? | ||
How do I get all of libcore's macros if my crate is `no_std`? | ||
|
||
Does `use_macros` also define the name in child modules? In a sense this is the | ||
more natural thing to do in libsyntax, but it's inconsistent with normal `use` | ||
items. | ||
|
||
All of the syntax is bikeshedable. For example, should `use_macros` include | ||
the exclamation point? What about when it applies to item decorator attributes? | ||
|
||
Allowing `$crate` in attributes is weird. Maybe we should use some other | ||
syntax that fits with existing attribute parsing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I stated on #440, I don't like taking this nice name for this imperfect macro system. It seems unfortunate to me that one would have to explicitly opt-in to some new improved macro system if/when we create a parallel one that incorporates the backwards-incompatible improvements to
macro_rules!
. (That is, it is extremely unfortunate that the obviousmacro! foo { ... }
invocation would give the deprecated form.)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename
trait
tocrappy_trait
because we don't have HKT yet.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, maybe that's an unfair comparison because HKT are supposed to be back-compat. But every macro system we ever have in Rust is going to be "imperfect" in some way. At some point we have to live with the imperfections and improve them in non-breaking ways, which is what 1.0 is all about, but why back away from that idea just in the case of macros?
The practical outcome of your alternative is that Rust programmers are stuck with the ugly name
macro_rules!
for perhaps years and the unstable alternative will be something not descriptively different, likemacro_case!
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With macros, we already know before 1.0 that the ways we want to improve them in the future are not backwards compatible, and the stabilization of the current system of macros is itself a compromise. I don't know of another Rust language feature in a similar situation, but maybe there is an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically I see it as
macro_rules!
, for years#![feature(improved_macros)]
or#[version(2)] macro!
, if and when such back-incompat enhancements are developedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But long term, they'll be on Rust 2.x or Rust 3.x, which gives us another chance to make breaking changes, especially fairly superficial ones like naming.
I'm not sure how long 1.x is meant to last but it would seem relevant to this discussion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing to note: some of the ways that macros will change, like hygiene, will be (a) a change to the entire system, preventing backwards compatibility from even being possible (i.e.,
define_a_macro_in_the_old_way!
won't recover the old semantics), and (b) make things almost strictly better (i.e., the old semantics mainly gets in people's way).However, the reason it is called
macro_rules!
is that it's not able to define all kinds of macros, only rule-based ones. A construct namedmacro!
should be capable of defining procedural macros, and I think that the arrival of procedural macros is an appropriate time to make the name change.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was expecting that we would have two essentially independent hygiene systems inside the compiler (i.e. identifiers would go from storing just
ctxt: SyntaxContext
to storingctxt: SyntaxContext, improved_ctxt: SyntaxContext
); it's pretty sad, but it seems to me to be the only way we can possibly aim to improve hygiene without breaking backwards compatibility.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@huonw this is horrifying! I was expecting that we would be able to have a single hygiene system in rustc and any macro system could do what they like - i.e., rustc::resolve would expect all lexical context ids to be 'correct'. macro! would output correct lexical contexts, macro_rules! would output lexical contexts that correspond to the current level of hygiene, etc. I'm not entirely sure if this is possible, because I don't exactly know how hygiene is currently broken.
As far as I see it, being able to do this backwards compatibly after 1.0 is the most important issue for macros pre-1.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nick29581 I agree 100% that it is not nice, but I believe that it is an existence proof that we can have a macro system with improved hygiene post-1.0 without being forced to break what we already have. (Your approach does sound better, if it works.)