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 32 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
351 changes: 351 additions & 0 deletions text/2442-simple-postfix-macros.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,351 @@
- Feature Name: `simple_postfix_macros`
- Start Date: 2018-05-12
- RFC PR: [rust-lang/rfcs#2442](https://github.com/rust-lang/rfcs/pull/2442)
- 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.

However, unlike macro syntax, postfix syntax can *only* be used by language
extensions; it's not possible to experiment with postfix syntax in macro-based
extensions to the language. Language changes often start through
experimentation, and such experimentation can also result in sufficiently good
alternatives to avoid requiring language extensions. Having a postfix macro
syntax would serve both goals: enabling the prototyping of new language
features, and providing compelling syntax without having to extend the
language.

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, inspired specifically by `try!(expr)` becoming `expr?`
and `await!(expr)` becoming `expr.await`, without blocking potential future
extensions. In particular, this RFC does not in any way preclude a future
implementation of postfix macros with full type-based or trait-based dispatch.

# 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) => ({
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, a macro `do_thing` could allow
both of the following:

```rust
do_thing!(some_expression());
some_other_expression().do_thing!().further_expression().do_thing!();
```

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.

A postfix macro may accept `self` by value, by reference, or by mutable
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
reference; the compiler will automatically use the appropriate type of
reference, just as it does for closure captures.

# Reference-level explanation
joshtriplett marked this conversation as resolved.
Show resolved Hide resolved
[reference-level-explanation]: #reference-level-explanation

When expanding a postfix macro, the compiler will effectively create a
temporary binding (as though with `match`) 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 change avoids any potential ambiguities regarding the scope of
the `$self` argument and how much it leaves unevaluated, by evaluating it
fully.

In the following expansion, `k#autoref` represents an internal compiler feature
within pattern syntax (which this RFC does not propose exposing directly), to
invoke the same compiler machinery currently used by closure captures to
determine whether to use `ref`, `ref mut`, or a by-value binding.

Given the following macro definition:

```rust
macro_rules! log_value {
($self:self, $msg:expr) => ({
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");
}
```

The invocation in `main` will expand to the following:

```rust
match value("hello") {
k#autoref _internal1 => match ({
eprintln!("{}:{}: {}: {:?}", "src/main.rs", 14, "value", _internal1);
_internal1
}
.len())
{
k#autoref _internal2 => {
eprintln!("{}:{}: {}: {:?}", "src/main.rs", 14, "len", _internal2);
_internal2
}
},
};
```

The first `match` represents the expansion of the first `log_value!`; the
second `match` represents the expansion of the second `log_value!`. The
compiler will generate unique symbols for each internal variable.

The use of `match` in the desugaring ensures that temporary lifetimes last
until the end of the expression; a desugaring based on `let` would end
temporary lifetimes before calling the postfix macro.

The use of `k#autoref` in the desugaring allows a postfix macro to work in
contexts such as `some_struct.field.mac!()` (in which the macro must accept
`&self` by reference to avoid moving out of the struct field), as well as in
contexts that must take ownership of the receiver in order to function.

Note that postfix macros cannot dispatch differently based on the type of the
expression they're invoked on. The internal binding the compiler creates for
that expression will participate in type inference as normal, including the
expanded body of the macro. If the compiler cannot unambiguously determine the
type of the internal binding, it will produce a compile-time error. If the
macro wishes to constrain the type of `$self`, it can do so by writing a `let`
binding for `$self` with the desired type.

Macros defined using this mechanism follow exactly the same namespace and
scoping rules as any other macro. If a postfix macro is in scope, Rust code may
call it on any object.

A macro may only be called as a postfix macro if it is directly in scope as an
unqualified name. A macro available via a qualified path (for instance,
`path::to::macro`) does not support postfix calls. If an imported macro uses
`as` to rename it (for instance, `use path::to::macro_name as other_name`), it
supports postfix calls using the new name, not the original name.

Even though `$self` represents an internal temporary location provided by the
compiler, calling `stringify!` on `$self` will return a stringified
representation of the full receiver expression. For instance, given
`a.b()?.c.m!()`, `stringify!($self)` will return `"a.b()?.c"`. This allows
postfix macros to provide functionality such as `dbg!` or `assert!` that wants
to show the receiver expression.

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. A postfix macro can trivially forward its
arguments to a proc macro.

Macros have historically not interacted with the type system, while method
calls (`expr.method()`) do type-based dispatch based on the type or trait of
the expression. In introducing postfix macros that look similar to method
calls, this proposal does not attempt to introduce type-based dispatch of
macros at the same time; an invocation `expr.m!()` does not in any way depend
on the type of `expr` (though the expansion of the macro may have expectations
about that type that the compiler will still enforce). A future RFC may
introduce type-based dispatch for postfix macros; however, any such future RFC
seems likely to still provide a means of writing postfix macros that apply to
any type. This RFC provides a means to implement that subset of postfix macros
Comment on lines +268 to +271
Copy link
Contributor

Choose a reason for hiding this comment

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

I can say with confidence that there is no support for this in T-types.

Furthermore we don't have the infrastructure in the compiler for supporting this, and are not expecting this to be possible within the next 5-10 years. There is some serious reengineering required to get there, and the incremental steps required for it have stalled two years ago.

that apply to any type, enabling a wide range of language experimentation
(particularly in readability and usability) that has previously required
changes to the language itself.

# 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)`).
Comment on lines +279 to +292
Copy link
Contributor

@oli-obk oli-obk Feb 19, 2024

Choose a reason for hiding this comment

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

edit: wrong text section highlighted, so read the comment as a free comment

I think this RFC needs to be evaluated on the basis that such a system will never come to Rust, as that is the likeliest situation we'll find ourselves in afaict.

If this RFC is only accepted because it leaves the door open to a future type based extension, then it should not be accepted imo.

If this section causes opponents of the RFC to accept the RFC as a compromise, because a type based system is expected to come in the future, then this should be rediscussed.

Imo this RFC should explicitly state that we will never get a type based system, and include T-types in the FCP.


Rather than writing `expr.macroname!()`, we could write `expr.!macroname()` or
similar, placing the `!` closer to the `.` of the method call. This would place
greater attention on the invocation, but would break the similarity with
existing macro naming that people have grown accustomed to spotting when
reading code. This also seems more likely to get confused with the prefix unary
`!` operator.

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.

In the syntax to define a postfix macro, rather than using
```
($self:self, $arg:expr) => (...)
```
we could use
```
$self:self.($arg:expr) => (...)
```
. This would have the advantage of looking more similar to the invocation, but
the disadvantage of looking much different than method definition syntax (in
which `self` appears within the function arguments).

In the syntax to define a postfix macro, rather than using
```
($self:self, $arg:expr) => (...)
```
we could use
```
($self:self. $arg:expr) => (...)
```
or
```
($self:self $arg:expr) => (...)
```
. Using a `.` might be evocative of method syntax, but would be unusual and
harder to remember. Using no delimiter at all would reflect that the `,` does
not actually match a literal `,` in syntax, and might be clearer when using
macros whose arguments use unusual syntax substantially different than method
arguments (e.g. `expr.mac!(:thwack => boing | :poit <= narf)`) but would be
confusing and error-prone for macros intended to resemble method calls (e.g.
`expr.mac!(arg1, arg2, arg3)`). (Making the `,` optional seems even more
confusing and prone to ambiguity.)

We could omit the `k#autoref` mechanism and only support `self`. However, this
would make `some_struct.field.postfix!()` move out of `field`, which would make
it much less usable.

We could omit the `k#autoref` mechanism in favor of requiring the macro to
specify whether it accepts `self`, `&self`, or `&mut self`. However, this would
prevent writing macros that can accept either a reference or a value, violating
user expectations compared to method calls (which can accept `&self` but still
get called with a non-reference receiver).
Comment on lines +342 to +350
Copy link
Contributor

Choose a reason for hiding this comment

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

Why can't we just use &$self as an expression and have that evaluate to the tokens that were passed as the self? Just like with other arguments, if you don't want it evaluated twice, first evaluate it into a let binding, then use that twice.


Rather than requiring a macro to explicitly declare that it works with postfix
syntax using a `:self` specifier, we could allow calling existing macros using
postfix syntax, and automatically pass the receiver as the first argument. This
would work similarly to [Uniform Function Call
Syntax](https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax), as well as
similarly to the ability to call `Type::method(obj, arg)` rather than
`obj.method(arg)`. Working with all existing macros would be both an advantage
and a disadvantage. This RFC proposes to require macros to explicitly opt-in to
postfix syntax, so that the macro has control over whether it makes sense in
postfix position, and so that the macro can determine what syntax makes the
most sense in postfix position. An explicit opt-in also seems appropriate
given the pre-evaluation behavior of the `:self` specifier, which seems
sufficiently different to warrant a different specifier rather than existing
specifiers like `:expr`.

We could allow postfix macros to omit the delimiters entirely when they have no
arguments. For instance, instead of `a.b()?.c.mac!()`, we could allow writing
`a.b()?.c.mac!`. This would work well when using a macro as a substitute for a
postfix keyword (similar to `.await` or `?`). The `!` would remain, to indicate
the invocation of a macro. However, some macros may prefer to look like a
zero-argument method call (`.mac!()`). Allowing *both* `.mac!` and `.mac!()`
introduces potential ambiguities. If we want to allow `.mac!` in the future, we
could provide an opt-in syntax for the macro to use, which would allow omitting
the delimiter; if, in the future, we determine that we can unambiguously allow
a macro to support *optional* parentheses, we could allow opting into both.
This RFC proposes that we always require the delimiter for now, for simplicity
and to avoid ambiguity.

# Prior art
[prior-art]: #prior-art

The evolution of `try!(expr)` into `expr?`, and the evolution of `await!(expr)`
into `expr.await`, both serve as prior art for moving an important macro-style
control-flow mechanism from prefix to postfix.

# Future work

- We may also want a means of creating postfix proc macros.
- We *may* want to expose `k#autoref` for other purposes. We may also want to
use it in the definition of other syntax desugaring in the future.