Skip to content
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

Macros by example 2.0 (macro!) #1584

Merged
merged 3 commits into from
Jan 30, 2017
Merged

Macros by example 2.0 (macro!) #1584

merged 3 commits into from
Jan 30, 2017

Conversation

nrc
Copy link
Member

@nrc nrc commented Apr 19, 2016

Macros by example 2.0. A replacement for macro_rules!. This is mostly a
placeholder RFC since many of the issues affecting the new macro system are
(or will be) addressed in other RFCs. This RFC may be expanded at a later date.

Rendered

Tracking issue

@nrc nrc self-assigned this Apr 19, 2016
@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 19, 2016
@durka
Copy link
Contributor

durka commented Apr 19, 2016

Hmm so we are going to have three macro systems? That is, legacy macro_rules!, macro!, and procedural macros. Though I assume that the first two should be implementable using procedural macros, right?

I am curious about the specific, substantive, backwards-incompatible improvements to Macro By Example that motivate this. Because there have been many useful, backwards-compatible improvements proposed that have been shoved to the side in anticipation of procedural macros.


mod a {
// Macro privacy (TBA)
pub macro! foo { ... }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the point of the ! after the keyword? I see no reason for that. Perhaps more fitting would be a ! after foo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency with macro_rules!. Dropping the ! is listed in the alternatives section.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These already are incompatible systems and there’s no reason to have such weird consistency quirk IMO.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't get the point. There is no compatibility anyways.

Copy link

@nixpulvis nixpulvis Apr 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment in #1561 (comment)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ticki What effect could an import possibly have that the programmer cares about? Macros are only potentially surprisingly at the invocation site.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@solson What if you have a macro named foo! and a function named foo, and you want to export them both for other modules to import? Those two names do not conflict; see https://is.gd/QCJ7Iv for an example. How would you disambiguate and allow importing those names independently? Including the ! on the import of the macro seems like it would produce the behavior people would expect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joshtriplett Yeah, it might be necessary for name clashes. @eternaleye also brought up the fact that with procedural macros you would usually want to import the macro, but you might also want to import the fn that implements the macro, which has the same name, so you could call it as a subroutine of another procedural macro.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@solson: that latter case is especially meaningful because using it as a macro means the compiler needs it at build time, while using it as a function means the crate needs it at run time. As a result, importing both and relying on them being called in different ways does not suffice.

Copy link
Contributor

@Ericson2314 Ericson2314 Sep 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was going to propose extern! for phase-incrementing imports. See rust-lang/rust#35900 (comment) in the procedural macros 1.1 tracking issue where I brought up the problem.

@nrc
Copy link
Member Author

nrc commented Apr 19, 2016

Hmm so we are going to have three macro systems? That is, legacy macro_rules!, macro!, and procedural macros.

Well in the short term, four - old and new macros by example, and old and new procedural macros. In the long term, hopefully, both old versions will disappear.

Though I assume that the first two should be implementable using procedural macros, right?

I don't consider this a goal. While it would be nice (it's kind of cute from a language geek perspective), it doesn't bring any material advantages (making macros by example 'pluggable' is about the only real advantage, and I don't think that is a great one, others may differ). So, I'm not trying to avoid this, but if there are benefits we get by not doing it then, we'll take them.

specific, substantive, backwards-incompatible improvements to Macro By Example

  • naming and modularisation (i.e., naming macros like other items in Rust)
  • privacy for macros
  • being able to use relative paths in macros (path hygiene)
  • better hygiene (e.g., for items, type parameters, lifetime parameters, etc.)
  • potentially, some nicer syntax

@durka
Copy link
Contributor

durka commented Apr 19, 2016

How about

  • working concat_idents!
  • more matchers (lifetime, visibility, moelarry)
  • a way to escape $
  • macro re-exportation (sounds like it'll be covered under modularisation)
  • one-or-zero repetition

How many macro systems do we need to go through before feature requests like that can be considered? I don't mean to sound sarcastic -- I'm excited, but a bit dismayed about continual punting.

@durka
Copy link
Contributor

durka commented Apr 19, 2016

I actually meant "can we implement Macro By Example 2.0 using new procedural macros" as a serious suggestion, not a cute one. If procedural macros are powerful enough and ergonomic enough to do it, then we can iterate on Macro By Example features without arguing an RFC for each one.

@nrc
Copy link
Member Author

nrc commented Apr 19, 2016

working concat_idents!

RFC coming up in the next few weeks

a way to escape $

part of the new syntax, should be coming soon

macro re-exportation (sounds like it'll be covered under modularisation)

export/re-export disappears, replaced by modularisation

more matchers (lifetime, visibility, moelarry)
one-or-zero repetition

backwards compatible, I'm not planning anything here, but I'd be happy to see RFCs building on macros 2.0.

In short, I see macros 2.0 as a prerequisite for this kind of stuff - we don't want to expend effort on them until we're sure about the foundations. I don't see macro reform as punting that stuff, more like bringing it closer.

@durka
Copy link
Contributor

durka commented Apr 19, 2016

Awesome!

I'd be happy to see RFCs building on macros 2.0

That's what I wanted to hear :)

@kennytm
Copy link
Member

kennytm commented Apr 30, 2016

cc #1266. I think having a way to generate a macro-private mod (i.e. creating a hygienic item) should be enough for most use cases of gensym.

Also, would this address #991 (use + or * as separators)?

@nrc
Copy link
Member Author

nrc commented Nov 11, 2016

@rfcbot fcp merge

This RFC proposes the concept of macros by example 2.0, without defining the details. Together with other RFCs, some parts (e.g., naming and modularisation) could be implemented today. Future RFCs will further specify other aspects of macros 2.0. It is somewhat akin to the proposed concept of motivation RFCs, although there is a small amount of implementation specified.

Feedback has been positive, with the only controversial point whether to use macro or macro! to declare macros. I have changed the RFC from the latter to the former.

An alternative to accepting this RFC now, might be to accept some implementation in the compiler without an accepted RFC and fill out this RFC into a more complete version.

@rfcbot
Copy link
Collaborator

rfcbot commented Nov 11, 2016

Team member @nrc has proposed to merge this. The next step is review by the rest of the tagged teams:

No concerns currently listed.

Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

@withoutboats
Copy link
Contributor

withoutboats commented Nov 11, 2016

Reviewed the RFC again and I like it.

I am still pretty strongly in favor of macro!. I hold the perhaps too optimistic perspective that some day macro!, format_args!, #[proc_macro_derive], and possibly some of the other "compiler built ins" can simply be libraries on top of the procedural macro API. I think the conceptual elegance of having a single metaprogramming layer with a bunch of tools written in it would make the language easier to "get" and more appealing to users, and I think holding out for that is worth the cost of having to write macro! instead of macro.

This isn't to say that we should have implementing the APIs to make this possible in our near horizon, just that we shouldn't foreclose on the possibility. Maybe the idea of ever achieving this is unrealistic though.

@joshtriplett
Copy link
Member

I'd like to see macro! for similar reasons.

@solson
Copy link
Member

solson commented Nov 11, 2016

I don't understand why the thing that defines macro items needs to look like a macro invocation. I'm pretty strongly in favor of plain macro. Today's macro_rules! presents a bit of a weird separate world from regular Rust syntax, and using a ! for the declaration is one way it differs from normal Rust items. Why not make macros more normal?

I find it even more troubling to use the ! when the RFC proposes to attach a privacy to the front. pub macro! foo(...) reads like a weird mix of treating macros like first class items but also not.

@eddyb
Copy link
Member

eddyb commented Nov 11, 2016

Not sure where or even if it was mentioned, but pub macro foo = macro_rules! {...}; might work.

@withoutboats
Copy link
Contributor

withoutboats commented Nov 11, 2016

I don't understand why the thing that defines macro items needs to look like a macro invocation.

So that it might be a macro invokation someday.

(I'm very interested in arguments that this wouldn't be possible!)

@solson
Copy link
Member

solson commented Nov 11, 2016

I don't know about possible, it just doesn't seem desirable, unless it looked like eddyb's syntax. It really doesn't fit well with pub and being a proper item.

@eddyb
Copy link
Member

eddyb commented Nov 11, 2016

@withoutboats What language primitive would it expand to? I'd be more interested in figuring out a method of declaration with a pluggable "body". Although purely token-based expansion might not cut it either way.

@withoutboats
Copy link
Contributor

withoutboats commented Nov 11, 2016

@eddyb it would need to expand to a proc macro, I suppose.

@solson more generally I am in favor of someday having a proc macro form for things that look like items but YMMV on that idea.

@keeperofdakeys
Copy link

keeperofdakeys commented Nov 11, 2016

From a user's perspective, I can't think of a consistent way of making macro! behave like other item definitions. So using the macro keyword seems like the best choice for now. If this is the wrong choice, I'm sure there will be plenty of chances to change this down the line.

@solson
Copy link
Member

solson commented Nov 11, 2016

@withoutboats I'm not opposed to a more general macro syntax concept that works for item-like things. I just think the macro! idea being proposed right now doesn't fit into the existing syntax.

@nikomatsakis
Copy link
Contributor

@withoutboats

So that it might be a macro invokation someday.

(I'm very interested in arguments that this wouldn't be possible!)

The argument that I remember has to do with cross compilation: macros defined in other crates can be used, even if those crates are not compiled for your current target. I'm not sure if this could be easily "simulated" using plugins.

@nikomatsakis
Copy link
Contributor

So, I left my check on this RFC, but to be clear -- all we're basically giving agreement to here is the idea of a macro 2.0 system, right? All details (e.g., syntax, probably even macro vs macro!) remain for future RFCs?

@withoutboats
Copy link
Contributor

@solson When you say it doesn't fit into existing syntax, does that mean you wouldn't favor a macro extension which would allow you to define macros that look like e.g. pub actor! Mailer { }, pub class! AbstractManagerFactory { } etc?

@solson
Copy link
Member

solson commented Nov 11, 2016

@withoutboats Like I said, I'm not opposed to a more general macro syntax concept that works for item-like things, so I would be fine with that. When I say it doesn't fit into existing syntax, I really mean existing syntax. :) It would fit fine if we introduced this new general thing.

(It would fit fine syntactically, at least - @eddyb's question about what it expands into is an important one.)

@withoutboats
Copy link
Contributor

withoutboats commented Nov 11, 2016

@solson but if we don't include the bang now, we can never make it use that new syntax extension? It has to be built into the compiler forever.

@nikomatsakis
Copy link
Contributor

I prefer the name "pattern macro" to "declarative macro".

@steveklabnik
Copy link
Member

@nikomatsakis can you say more? What do you think about the declarative/procedural split? I mostly prefer declarative due to the symmetry, but could be swayed 😄

@Ixrec
Copy link
Contributor

Ixrec commented Jan 27, 2017

+1 for "pattern macro". That's the only name I've heard for it which might give newcomers a somewhat accurate impression of what the feature actually is.

Although I also like the symmetry of "declarative" vs "procedural", the word "declarative" is so much vaguer than "pattern" that it doesn't mean much of anything without context.

@solson
Copy link
Member

solson commented Jan 27, 2017

@nikomatsakis That seems potentially confusing with macros that expand to patterns.

i.e. one might call foo in match x { foo!(42) => ... } a pattern macro

@ZhangHanDong
Copy link

+1 for "pattern macro"

nrc added 3 commits January 31, 2017 09:28
Macros by example 2.0. A replacement for `macro_rules!`. This is mostly a
placeholder RFC since many of the issues affecting the new macro system are
(or will be) addressed in other RFCs. This RFC may be expanded at a later date.
Only major change is moving from `macro!` to `macro` to declare a macro.
@nrc nrc merged commit 7dcb737 into rust-lang:master Jan 30, 2017
@elahn
Copy link

elahn commented Jan 31, 2017

"regular macro" - rule-governed, conforming to a pattern.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Feb 1, 2017

@steveklabnik

@nikomatsakis can you say more? What do you think about the declarative/procedural split? I mostly prefer declarative due to the symmetry, but could be swayed 😄

The reason I prefer "pattern macro" is that we are applying a series of patterns, one by one, and testing for a match. "Declarative" macro is also ok, but I think that the sense in which "declarative" vs procedural is being used here is ultimately a kind of jargon, and the name just won't "stick" as easily as pattern.

I suppose there could be some confusion regarding a macro that expands to a pattern, but I feel like I have basically never categorized a macro by what it expands to in this way -- that is, I don't say "it's an expression macro". I say, "it's a macro that generates an expression". (Actually, I don't say that at all most of the time, since I never seem to find I have to clarify this.)

@steveklabnik
Copy link
Member

Seems fine. I don't care super strongly about this, and I think that reasoning is good too.

@colin-kiegel
Copy link

colin-kiegel commented Feb 1, 2017

+1 for pattern macro

I think pattern matching is an essential part of what you can do with them. And declarative doesn't really tell me anything. It feels a bit academic to call it declarative but is not does not help me understand what it does.

@solson
Copy link
Member

solson commented Feb 1, 2017

Please avoid "+1" comments with no content. GitHub added the 👍 reaction so we could avoid getting emails about nothing. :)

@nrc nrc deleted the macros-2.0 branch February 2, 2017 04:57
@nrc
Copy link
Member Author

nrc commented Feb 2, 2017

I merged this and forgot to leave a summary at the time.

Tracking issue: rust-lang/rust#39412

This RFC is primarily a statement of intent that we will add a new declarative macros system to Rust (macros 2.0). The only real detail is that we will use macro (c.f., macro_rules!) to declare such macros (while leaving the specific syntax for future work). There has been some discussion of that in the thread, in particular about whether to use macro or macro!. The key point in that debate is that whatever the underlying mechanism for macro expansion, one can think of a desugaring step from the macro syntax, into that mechanism.

The other contentious point seems what to call these things. I added some options to the RFC before merging. My preference is for 'declarative macro' and I merged the RFC with that (I don't think it is worth postponing merging just for this). My feeling is that for now we need a name that is useful for implementors since we have to implement the feature, and we don't need to describe it to users (well, not much, anyway) until it is implemented. The main part of the name for me is its opposition to procedural macros, i.e., these are macros defined using declarative syntax, rather than Rust code. I prefer 'declarative' to 'pattern' since there might be declarative macros which do not rely on pattern matching so centrally, and which might be a candidate for macros 2.0 (I don't have anything in mind here, just not shutting any doors). When it comes to describing macros to users, I personally think 'declarative' is fine, but we could also use 'pattern macro' informally - I don't think renaming features should require an RFC or anything.

@ZhangHanDong
Copy link

Well, You're right. @nrc

bors added a commit to rust-lang/rust that referenced this pull request May 22, 2017
Initial implementation of declarative macros 2.0

Implement declarative macros 2.0 (rust-lang/rfcs#1584) behind `#![feature(decl_macro)]`.
Differences from `macro_rules!` include:
 - new syntax: `macro m(..) { .. }` instead of `macro_rules! m { (..) => { .. } }`
 - declarative macros are items:
```rust
// crate A:
mod foo {
    m!(); // use before definition; declaration order is irrelevant
    pub macro m() {} // `pub`, `pub(super)`, etc. work
}
fn main() {
    foo::m!(); // named like other items
    { use foo::m as n; n!(); } // imported like other items
}
pub use foo::m; // re-exported like other items

// crate B:
extern crate A; // no need for `#[macro_use]`
A::foo::m!(); A::m!();
```
 - Racket-like hygiene for items, imports, methods, fields, type parameters, privacy, etc.
   - Intuitively, names in a macro definition are resolved in the macro definition's scope, not the scope in which the macro is used.
   - This [explaination](http://beautifulracket.com/explainer/hygiene.html) of hygiene for Racket applies here (except for the "Breaking Hygiene" section). I wrote a similar [explanation](https://github.com/jseyfried/rfcs/blob/hygiene/text/0000-hygiene.md) for Rust.
   - Generally speaking, if `fn f() { <body> }` resolves, `pub macro m() { <body> } ... m!()` also resolves, even if `m!()` is in a separate crate.
   - `::foo::bar` in a `macro` behaves like `$crate::foo::bar` in a `macro_rules!`, except it can access everything visible from the `macro` (thus more permissive).
   - See [`src/test/{run-pass, compile-fail}/hygiene`](afe7d89) for examples. Small example:
```rust
mod foo {
    fn f() { println!("hello world"); }
    pub macro m() { f(); }
}
fn main() { foo::m!(); }
```

Limitations:
 - This does not address planned changes to matchers (`expr`,`ty`, etc.), c.f. #26361.
 - Lints (including stability and deprecation) and `unsafe` are not hygienic.
   - adding hygiene here will be mostly or entirely backwards compatible
 - Nested macro definitions (a `macro` inside another `macro`) don't always work correctly when invoked from external crates.
   - pending improvements in how we encode macro definitions in crate metadata
 - There is no way to "escape" hygiene without using a procedural macro.

r? @nrc
bors added a commit to rust-lang/rust that referenced this pull request May 25, 2017
Initial implementation of declarative macros 2.0

Implement declarative macros 2.0 (rust-lang/rfcs#1584) behind `#![feature(decl_macro)]`.
Differences from `macro_rules!` include:
 - new syntax: `macro m(..) { .. }` instead of `macro_rules! m { (..) => { .. } }`
 - declarative macros are items:
```rust
// crate A:
pub mod foo {
    m!(); // use before definition; declaration order is irrelevant
    pub macro m() {} // `pub`, `pub(super)`, etc. work
}
fn main() {
    foo::m!(); // named like other items
    { use foo::m as n; n!(); } // imported like other items
}
pub use foo::m; // re-exported like other items

// crate B:
extern crate A; // no need for `#[macro_use]`
A::foo::m!(); A::m!();
```
 - Racket-like hygiene for items, imports, methods, fields, type parameters, privacy, etc.
   - Intuitively, names in a macro definition are resolved in the macro definition's scope, not the scope in which the macro is used.
   - This [explaination](http://beautifulracket.com/explainer/hygiene.html) of hygiene for Racket applies here (except for the "Breaking Hygiene" section). I wrote a similar [explanation](https://github.com/jseyfried/rfcs/blob/hygiene/text/0000-hygiene.md) for Rust.
   - Generally speaking, if `fn f() { <body> }` resolves, `pub macro m() { <body> } ... m!()` also resolves, even if `m!()` is in a separate crate.
   - `::foo::bar` in a `macro` behaves like `$crate::foo::bar` in a `macro_rules!`, except it can access everything visible from the `macro` (thus more permissive).
   - See [`src/test/{run-pass, compile-fail}/hygiene`](afe7d89) for examples. Small example:
```rust
mod foo {
    fn f() { println!("hello world"); }
    pub macro m() { f(); }
}
fn main() { foo::m!(); }
```

Limitations:
 - This does not address planned changes to matchers (`expr`,`ty`, etc.), c.f. #26361.
 - Lints (including stability and deprecation) and `unsafe` are not hygienic.
   - adding hygiene here will be mostly or entirely backwards compatible
 - Nested macro definitions (a `macro` inside another `macro`) don't always work correctly when invoked from external crates.
   - pending improvements in how we encode macro definitions in crate metadata
 - There is no way to "escape" hygiene without using a procedural macro.

r? @nrc
@U007D
Copy link

U007D commented Oct 26, 2017

Just a quick comment on an old thread to say I love love love the tone and civility of this thread. Special hat tip to @withoutboats for so eloquently expressing ideas and concerns even as they were partially formed--publicly sharing ideas before they are fully formed is both very difficult and courageous.

Huge respect and thanks to all--many people (myself included) are the beneficiaries of this discussion (and not just its technical content).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Macro related proposals and issues A-syntax Syntax related proposals & ideas final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.