-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Simple postfix macros #2442
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
base: master
Are you sure you want to change the base?
Simple postfix macros #2442
Changes from 1 commit
684fae5
86c29a3
bdf9e41
e35414c
423237e
b4544df
b0605db
221de8e
0ebe83e
24999bb
dac0943
cd18fc9
81e1e8d
be68f24
f32688f
c9cb3be
fcf7a57
68d7673
3e99287
f2d4bd0
07c26b6
21cd66c
4467a5e
e0cb6bc
d966d41
293e8d6
df0b8f7
82da7f9
446b802
32ba75f
5711eca
c13ba49
731dbc6
a409770
37b333d
0194091
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,171 @@ | ||
- Feature Name: `simple_postfix_macros` | ||
- Start Date: 2018-05-12 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Allow simple postfix macros, of the form `expr.ident!()`, to make macro | ||
invocations more readable and maintainable in left-to-right method chains. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
The transition from `try!` to `?` doesn't just make error handling more | ||
concise; it allows reading expressions from left to right. An expression like | ||
`try!(try!(try!(foo()).bar()).baz())` required the reader to go back and forth | ||
between the left and right sides of the expression, and carefully match | ||
parentheses. The equivalent `foo()?.bar()?.baz()?` allows reading from left to | ||
right. | ||
|
||
The introduction of `await!` in RFC 2394 brings back the same issue, in the | ||
form of expressions like `await!(await!(await!(foo()).bar()).baz())`. This RFC | ||
would allow creating a postfix form of any such macro, simplifying that | ||
expression into a more readable `foo().await!().bar().await!().baz().await!()`. | ||
|
||
Previous discussions of method-like macros have stalled in the process of | ||
attempting to combine properties of macros (such as unevaluated arguments) with | ||
properties of methods (such as type-based or trait-based dispatch). This RFC | ||
proposes a minimal change to the macro system that allows defining a simple | ||
style of postfix macro, designed specifically for `await!` and for future cases | ||
like `try!` and `await!`, without blocking potential future extensions. | ||
|
||
# Guide-level explanation | ||
[guide-level-explanation]: #guide-level-explanation | ||
|
||
When defining a macro using `macro_rules!`, you can include a first argument | ||
that uses a designator of `self` (typically `$self:self`). This must appear as | ||
the first argument, and outside any repetition. If the macro includes such a | ||
case, then Rust code may invoke that macro using the method-like syntax | ||
`expr.macro!(args)`. The Rust compiler will expand the macro into code that | ||
receives the evaluated `expr` as its first argument. | ||
|
||
```rust | ||
macro_rules! log_value { | ||
($self:self, $msg:expr) => ({ | ||
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. Just adding a new matcher 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 think in order to be consistent with function syntax it should be 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. @petrochenkov That's exactly what I intended. I described the @est31 I considered that possibility; however, in addition to the inconsistency of not using a descriptor, that would limit potential future expansion a bit. Today, you can write 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. @joshtriplett 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. @petrochenkov Fixed. 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. @joshtriplett good point. Ultimately I don't care much about the actual syntax. |
||
eprintln!("{}:{}: {}: {:?}", file!(), line!(), $msg, $self); | ||
$self | ||
}) | ||
} | ||
|
||
fn value<T: std::fmt::Debug>(x: T) -> T { | ||
println!("evaluated {:?}", x); | ||
x | ||
} | ||
|
||
fn main() { | ||
value("hello").log_value!("value").len().log_value!("len"); | ||
} | ||
``` | ||
|
||
This will print: | ||
|
||
``` | ||
evaluated "hello" | ||
src/main.rs:14: value: "hello" | ||
src/main.rs:14: len: 5 | ||
``` | ||
|
||
Notice that `"hello"` only gets evaluated once, rather than once per reference | ||
to `$self`, and that the `file!` and `line!` macros refer to the locations of | ||
the invocations of `log_value!`. | ||
|
||
A macro that accepts multiple combinations of arguments may accept `$self` in | ||
some variations and not in others. For instance, `await!` could allow both of | ||
the following: | ||
|
||
```rust | ||
await!(some_future()); | ||
some_other_future().await!().further_future_computation().await!(); | ||
``` | ||
|
||
This method-like syntax allows macros to cleanly integrate in a left-to-right | ||
method chain, while still making use of control flow and other features that | ||
only a macro can provide. | ||
|
||
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation | ||
|
||
When expanding a postfix macro, the compiler will effectively create a | ||
temporary binding for the value of `$self`, and substitute that binding | ||
for each expansion of `$self`. This stands in contrast to other macro | ||
arguments, which get expanded into the macro body without evaluation. This | ||
joshtriplett marked this conversation as resolved.
Show resolved
Hide resolved
|
||
change avoids any potential ambiguities regarding the scope of the `$self` | ||
argument and how much it leaves unevaluated, by evaluating it fully. | ||
|
||
The `await!` macro, whether defined in Rust code or built into the compiler, | ||
would effectively have the following two cases: | ||
|
||
```rust | ||
macro_rules! await { | ||
($e:expr) => ({ | ||
// ... Current body of await! ... | ||
}) | ||
($self:$self) => ( | ||
joshtriplett marked this conversation as resolved.
Show resolved
Hide resolved
|
||
await!($self) | ||
) | ||
} | ||
``` | ||
|
||
Note that postfix macros cannot dispatch differently based on the type of the | ||
expression they're invoked on. This includes whether the expression has type | ||
`T`, `&T`, or `&mut T`. The internal binding the compiler creates for that | ||
expression will have that same type. | ||
|
||
Calling `stringify!` on `$self` will return `"$self"`. | ||
joshtriplett marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Using the `self` designator on any macro argument other than the first will | ||
produce a compile-time error. | ||
|
||
Wrapping any form of repetition around the `self` argument will produce a | ||
compile-time error. | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
Creating a new kind of macro, and a new argument designator (`self`) that gets | ||
evaluated at a different time, adds complexity to the macro system. | ||
|
||
No equivalent means exists to define a postfix proc macro; this RFC | ||
intentionally leaves specification of such means to future RFCs, for future | ||
development and experimentation. | ||
|
||
# Rationale and alternatives | ||
[alternatives]: #alternatives | ||
|
||
Rather than this minimal approach, we could define a full postfix macro system | ||
that allows processing the preceding expression without evaluation. This would | ||
require specifying how much of the preceding expression to process unevaluated, | ||
including chains of such macros. The approach proposed in this RFC does not | ||
preclude specifying a richer system in the future; such a future system could | ||
use a designator other than `self`, or could easily extend this syntax to add | ||
further qualifiers on `self` (for instance, `$self:self:another_designator` or | ||
`$self:self(argument)`). | ||
|
||
We could define a built-in postfix macro version of `await!`, without providing | ||
a means for developers to define their own postfix macros. This would | ||
also prevent developers from. | ||
joshtriplett marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
We could define a new postfix operator for `await!`, analogous to `?`. This | ||
would require selecting and assigning an appropriate symbol. This RFC allows | ||
fitting constructs that affect control flow into method chains without | ||
elevating them to a terse symbolic operator. | ||
|
||
We could do nothing at all, and leave `await!` in its current macro form, or | ||
potentially change it into a language keyword in the future. In this case, the | ||
problem of integrating `await` with method chains will remain. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
The evolution of `try!` into `?` serves as prior art for moving an important | ||
macro-style control-flow mechanism from prefix to postfix. `await!` has similar | ||
properties, and has already prompted discussions both of how to move it to | ||
postfix and how to integrate it with error handling using `?`. | ||
|
||
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Should we define a means of creating postfix proc macros, or can we defer that? | ||
- Does evaluating `$self` create any other corner cases besides `stringify!`? |
Uh oh!
There was an error while loading. Please reload this page.