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

Simple postfix macros #2442

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Changes from 10 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
684fae5
Simple postfix macros
joshtriplett May 15, 2018
86c29a3
Fix typo: $self:$self -> $self:self
joshtriplett May 16, 2018
bdf9e41
Finish incomplete sentence
joshtriplett May 16, 2018
e35414c
Explain the special-case for `stringify!`
joshtriplett May 16, 2018
423237e
Provide further rationale for forcing the evaluation of `$self`
joshtriplett May 16, 2018
b4544df
Explain use of `$self:self` rather than `$self`
joshtriplett May 16, 2018
b0605db
Document that `$self:self` must have either a comma or close paren af…
joshtriplett May 16, 2018
221de8e
Explicitly allow `$othername:self` but suggest `$self:self` by conven…
joshtriplett May 16, 2018
0ebe83e
Specify what designators `$self` will match if passed to another macro
joshtriplett May 16, 2018
24999bb
Explicitly state that a postfix macro can use any delimiters
joshtriplett May 16, 2018
dac0943
Clarify a reference to await
joshtriplett May 16, 2018
cd18fc9
Add a potential alternative syntax, diverging more from method call s…
joshtriplett May 16, 2018
81e1e8d
Further discussion of postfix macro syntax and non-type-based dispatch
joshtriplett May 16, 2018
be68f24
Clarify that postfix macros follow existing macro scope rules
joshtriplett May 21, 2018
f32688f
Explicitly state that this doesn't block future type-based dispatch
joshtriplett May 21, 2018
c9cb3be
Clarify type inference
joshtriplett Nov 7, 2020
fcf7a57
Add further guidance about proc macros
joshtriplett Nov 7, 2020
68d7673
Rewrite to cover .await as precedent
joshtriplett Nov 7, 2020
3e99287
Observe that macros can use let bindings for type constraints
joshtriplett Nov 8, 2020
f2d4bd0
Provide a full example of expansion
joshtriplett Nov 8, 2020
07c26b6
Set RFC PR URL
joshtriplett Nov 30, 2021
21cd66c
Document interaction with qualified paths
joshtriplett Nov 30, 2021
4467a5e
Rename file to match RFC number
joshtriplett Dec 1, 2021
e0cb6bc
Change desugaring to use `match` to preserve temporary lifetimes
joshtriplett Dec 1, 2021
d966d41
Support `&self` and `&mut self`, to handle `some_struct.field.postfix…
joshtriplett Dec 1, 2021
293e8d6
Make stringify! return a string representation of the full receiver e…
joshtriplett Apr 4, 2022
df0b8f7
Update "Unresolved questions" and add a "Future work" section
joshtriplett Apr 4, 2022
82da7f9
Postfix macros: Desugar to use the same autoref mechanism as closure …
joshtriplett Apr 4, 2022
446b802
Add alternate definition syntaxes to alternatives section
joshtriplett Apr 4, 2022
32ba75f
Document renaming of postfix macros on import
joshtriplett Apr 4, 2022
5711eca
Add alternative about UFCS-style use with any macro
joshtriplett Apr 4, 2022
c13ba49
Discuss alternative of omitting the delimiters for zero-argument post…
joshtriplett Apr 4, 2022
731dbc6
Explicitly document that a macro can support both postfix and non-pos…
joshtriplett Jan 17, 2024
a409770
Note future possibility of use in std (not part of this RFC)
joshtriplett Jan 17, 2024
37b333d
Add info about `stringify!` to guide-level explanation
joshtriplett Jan 17, 2024
0194091
Give examples of autoref behavior in guide-level explanation
joshtriplett Jan 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions text/0000-simple-postfix-macros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
- 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.
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved

```rust
macro_rules! log_value {
($self:self, $msg:expr) => ({
Copy link
Contributor

@petrochenkov petrochenkov May 15, 2018

Choose a reason for hiding this comment

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

Just adding a new matcher self would probably be enough - $anything: self.
During matching it wouldn't match anything except for a "method receiver" in expr.my_macro!(...), but during expansion it would work as expr.

Copy link
Member

Choose a reason for hiding this comment

The 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 $self as in ($self, $msg:expr) => ({.

Copy link
Member Author

Choose a reason for hiding this comment

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

@petrochenkov That's exactly what I intended. I described the :self as a new "designator", because the Rust documentation used that term. Do you mean something different when you describe it as a "matcher"?

@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 $self:expr and use $self, without the compiler attaching any special meaning to the use of the name self as a macro argument. So, making $self without a descriptor special seems inconsistent in a problematic way.

Copy link
Contributor

Choose a reason for hiding this comment

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

@joshtriplett
I see, the RFC never shows an example with $not_self: self, so I thought that $self is mandatory.

Copy link
Member Author

Choose a reason for hiding this comment

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

@petrochenkov Fixed.

Copy link
Member

Choose a reason for hiding this comment

The 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) => (
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.

Since `$self` represents an internal temporary location created by the
compiler, calling `stringify!` on `$self` will just return `"$self"`. If passed
to another macro, `$self` will only match a macro argument using a designator
of `:expr`, `:tt`, or `:self`.

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.

If the `$self:self` argument does not appear by itself in the macro argument
list (`($self:self)`, with the closing parenthesis as the next token after
`$self:self`), then it must have a `,` immediately following it, prior to any
other tokens. Any subsequent tokens after the `,` will match what appears
between the delimiters after the macro name in its invocation.

A macro may attach the designator `self` to a parameter not named `$self`, such
as `$x:self`. Using `$self:self` is a convention, not a requirement.

A postfix macro invocation, like any other macro invocation, may use any form
of delimiters around the subsequent arguments: parentheses (`expr.m!()`),
braces (`expr.m!{}`), or square brackets (`expr.m![]`).

# 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. Furthermore, unlike existing macros, which
wrap *around* the expression whose evaluation they modify, if a postfix macro
could arbitrarily control the evaluation of the method chain it postfixed, such
a macro could change the interpretation of an arbitrarily long expression that
it appears at the *end* of, which has the potential to create significantly
more confusion when reading the code.

The approach proposed in this RFC does not preclude specifying a richer system
in the future; such a future system could use a new 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 address
the specific issue with `await!`, but would not help developers create
solutions for similar future issues. This would perpetuate the problem of
requiring changes to the language and compiler to solve such problems, rather
than allowing developers to experiment with solutions in the broader Rust
ecosystem.

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.

In the syntax to define a postfix macro, we could use just `$self` rather than
`$self:self`. `$self` is not currently valid syntax, so we could use it for
this purpose without affecting any existing valid macro. This would make such
declarations look closer to a method declaration, which uses `self` without a
type. However, macros do currently allow `self` as the name of a macro argument
when used with a designator, such as `$self:expr`; this could lead to potential
confusion, and would preclude some approaches for future extension.

# 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!`?