Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upSimple postfix macros #2442
Conversation
This comment has been minimized.
This comment has been minimized.
|
I'm torn on this topic, but ultimately feeling favorable. On the one hand, sometimes I think "why don't we just make On the other hand, I think there is no fundamental reason that macro-expansion can't be interspersed with type-checking, a la Wyvern or (I think?) Scala. In that case, we could make On the gripping hand, that is so far out as to be science fiction, and the need for postfix macros is real today. Plus, if we ever get there — and indeed if we ever want to get there — I suppose that the |
petrochenkov
reviewed
May 15, 2018
| 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. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
petrochenkov
May 15, 2018
Contributor
Or maybe not since the macro doesn't accept tokens in this case, only a single interpolated token representing the whole receiver expression, so it may be okay to expand it later.
x.identity1!().identity2!() -> identity2!( $(x.identity1!()) ) -> x.identity1!() -> identity1!( $(x) ) -> x
This comment has been minimized.
This comment has been minimized.
petrochenkov
May 15, 2018
Contributor
Oh, you actually mean "evaluated", not "expanded" according to the log_value example.
I.e. if $self is used twice in a macro m called like complex_expr.m!(...), then complex_expr is evaluated only once and temporary variable with its result is automatically created and that variable is passed to the macro and not the expression itself?
That's something seriously novel and untypical for our macro system.
I'd expect the macro author to do it instead.
macro_rules! log_value {
($self:self, $msg:expr) => ({
let tmp = $self;
eprintln!("{}:{}: {}: {:?}", file!(), line!(), $msg, tmp);
tmp
})
}
This comment has been minimized.
This comment has been minimized.
joshtriplett
May 15, 2018
•
Author
Member
@petrochenkov Normally I'd agree, but I specifically went that route because it avoids any questions about how much of the preceding expression gets passed into the macro un-expanded and un-evaluated, which has been at issue in previous discussions of postfix macros.
I think it'd be really surprising to write something like a().b().c().d().m!() and have m receive the whole preceding a().b().c().d() (and all arguments to those functions), and potentially evaluate it more than once. That holds doubly true if those preceding bits include macros themselves. If I see m!(foo()) I expect that foo() might not get called; if I see foo().m!(), foo seems "outside" the macro, so I think it makes sense to know that foo() will always get called.
I do agree that the evaluation here makes this different than other macro arguments. However, that's part of how I'm positioning this as "simple" postfix macros; it specifically does not try to process tokens for that argument.
This comment has been minimized.
This comment has been minimized.
petrochenkov
May 15, 2018
Contributor
Ok, if macro expansion generates let tmp = $self; automatically (in principle, we can do this because we know that for this syntax both $self input and macro output are expressions), then the macro needs to decide how it consumes the result of evaluation - by value, by immutable reference, by mutable reference.
Manually it can be easily done using existing mechanisms:
let tmp = $self; // consume the receiver
let tmp = &$self; // borrow the receiver
let tmp = &mut $self; // borrow the receiverbut if it's done implicitly, then it needs some extra syntax - $self: &mut self or something.
This comment has been minimized.
This comment has been minimized.
joshtriplett
May 15, 2018
Author
Member
@petrochenkov My intention was that the macro doesn't get to decide that; $self has whatever type the "subject" expression does. If the preceding expression has type &mut T then the macro gets a &mut T.
If you get a T and you don't want to consume it, you can always let tmp = &mut $self yourself, and then return $self at the end after you no longer hold that reference.
The one added complication here: if the subject is mutable, should $self be effectively a let mut? That would allow, for instance, writing $self = ...;. I think the answer is yes, and I think the compiler can do that with the generated internal temporary, but it's worth thinking about.
That said: while I'm proposing doing it this way for simplicity's sake, if you think it's straightforward to do this another way, please let me know and I'm open to the possibility. My main argument against doing so is that doing so seems much harder and I don't want to make the perfect the enemy of the good. If doing so seems easy to you, I'd like to take that into account.
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
Please put https://github.com/rust-lang/rfcs/pull/2442/files#r188397176 somewhere in the RFC (perhaps in the alternatives section). I was also super surprised to find out that a mere macro matcher can change evaluation order.
This comment has been minimized.
This comment has been minimized.
joshtriplett
May 16, 2018
Author
Member
@durka I added a rationale for this design choice to the RFC.
This comment has been minimized.
This comment has been minimized.
ExpHP
May 16, 2018
•
If you get a T and you don't want to consume it, you can always let tmp = &mut $self yourself, and then return $self at the end after you no longer hold that reference.
I would like to see an example of code where such a macro has been expanded (or at least, a faithful attempt at representing the generated AST). I would assume that the macro automatically encloses its output in braces (to allow for the statement that binds $self), which means it is forced to move input lvalues, and thus let it = &$self; does nothing to prevent a.foo!() from moving a.
petrochenkov
reviewed
May 15, 2018
|
|
||
| ```rust | ||
| macro_rules! log_value { | ||
| ($self:self, $msg:expr) => ({ |
This comment has been minimized.
This comment has been minimized.
petrochenkov
May 15, 2018
•
Contributor
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.
This comment has been minimized.
This comment has been minimized.
est31
May 15, 2018
Contributor
$self as in ($self, $msg:expr) => ({.
This comment has been minimized.
This comment has been minimized.
joshtriplett
May 15, 2018
Author
Member
@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.
This comment has been minimized.
This comment has been minimized.
petrochenkov
May 15, 2018
Contributor
@joshtriplett
I see, the RFC never shows an example with $not_self: self, so I thought that $self is mandatory.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
est31
May 16, 2018
Contributor
@joshtriplett good point. Ultimately I don't care much about the actual syntax.
This comment has been minimized.
This comment has been minimized.
|
cc @nrc who hated the idea |
Centril
added
the
T-lang
label
May 15, 2018
This comment has been minimized.
This comment has been minimized.
|
Putting aside some of the more technical aspects of the RFC and focusing solely on the motivation... This is quite a beautiful RFC. I wholeheartedly support some form of postfix macros; Considering some other use cases:
Some wilder considerations, which are mostly contra-factual musings of mine...
|
est31
reviewed
May 15, 2018
| 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 |
This comment has been minimized.
This comment has been minimized.
est31
May 15, 2018
Contributor
Could you make a simple example of how expansion would look like? Just to make it a little bit more clear to the reader.
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
I wrote out an example: hopefully this is correct.
macro_rules! log {
($self:self) => {{
$self.log!("value");
$self
}};
($self:self, $label:expr) => {{
println!("{}: {:?}", $label, $self);
$self
}}
}
fn main0() {
42.log!();
}
fn main1() {
{
let _self = 42;
{
_self.log!("value");
_self
}
};
}
fn main2() {
{
let _self = 42;
{
{
let __self = _self;
{
println!("{}: {:?}", "value", __self);
__self
}
}
_self
}
};
}Does that look right?
This comment has been minimized.
This comment has been minimized.
ExpHP
May 16, 2018
•
(I'd like to copy my comment here that this expansion unconditionally moves lvalue "receivers.")
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
Well, yeah, that's a problem. We could extend it to $self:self, $self:&self, $self:&mut self, but that looks even weirder.
This comment has been minimized.
This comment has been minimized.
|
Bit |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
I noticed that
As an alternative could you cover the advantages of |
This comment has been minimized.
This comment has been minimized.
|
I didn't find this in the RFC but would be worth calling out: is the expectation that |
durka
reviewed
May 16, 2018
| ($e:expr) => ({ | ||
| // ... Current body of await! ... | ||
| }) | ||
| ($self:$self) => ( |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
|
||
| 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. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| `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"`. |
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
What would be the problem with stringifying the evaluated expression? Or the unevaluated one, for that matter.
This comment has been minimized.
This comment has been minimized.
joshtriplett
May 16, 2018
Author
Member
The expression doesn't get evaluated until runtime; the compiler just substitutes an internal temporary location. I didn't want stringify! to have undefined behavior or return the name of that internal temporary location, so I specified it explicitly. I've clarified this.
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
This is basically another limitation caused by pre-evaluation. expr.dbg!() doesn't work if you can't use stringify. You could make stringify magic but that sidesteps the actual issue.
Also, it's not just stringify -- it's any macro that expects an unevaluated expression.
macro_rules! is_ident {
($i:ident) => { true };
($e:expr) => { false }
}
macro_rules! log {
($self:self) => {{
println!("{:?}", is_ident!($self));
$self
}}
}
42.log!();What does this print? It seems quite surprising for it to print true, but impossible for it to print false.
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
I suppose you could say that within the body of a postfix macro, $self is a totally opaque expression which only matches :expr.
| 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. |
This comment has been minimized.
This comment has been minimized.
durka
May 16, 2018
Contributor
Please put https://github.com/rust-lang/rfcs/pull/2442/files#r188397176 somewhere in the RFC (perhaps in the alternatives section). I was also super surprised to find out that a mere macro matcher can change evaluation order.
This comment has been minimized.
This comment has been minimized.
|
I think I like this idea, especially given the
I suspect the tension between these two is a big reason we don't have postfix macros yet. |
This comment has been minimized.
This comment has been minimized.
|
Even just (And the trait for |
joshtriplett
added some commits
May 16, 2018
This comment has been minimized.
This comment has been minimized.
Thanks, that's the perfect explanation for what I was trying to get at. I'm going to incorporate that into a revision of the RFC.
That's why I'm specifically positioning this as "simple postfix macros". This doesn't preclude adding more complex postfix macros in the future, but it provides a solution for many kinds of postfix macros people already want to write. |
This comment has been minimized.
This comment has been minimized.
|
Reposting a comment that got hidden: It's not just macro_rules! is_ident {
($i:ident) => { true };
($e:expr) => { false }
}
macro_rules! log {
($self:self) => {{
println!("{:?}", is_ident!($self));
$self
}}
}
42.log!();What does this print? It seems quite surprising for it to print |
joshtriplett
added some commits
May 16, 2018
This comment has been minimized.
This comment has been minimized.
|
@durka Why couldn't it print Does that make sense? |
This comment has been minimized.
This comment has been minimized.
|
It makes sense when you put on compiler-colored glasses, knowing that it's expanded to this invisible temporary binding. But normally macros can tell that |
This comment has been minimized.
This comment has been minimized.
|
@durka I understand what you mean, and that thought crossed my mind too. On the other hand, it can't match |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
May 16, 2018
|
Some users will be confused that a postfix macro can change the control flow even though it looks like a method. This can lead to obfuscated code. I can imagine debugging code where But the feature also looks very useful and simple. |
This comment has been minimized.
This comment has been minimized.
Non-postfix macros can do the same. In both cases, I think the
I definitely wouldn't expect that to happen, any more than I'd expect
Thanks! |
This comment has been minimized.
This comment has been minimized.
|
Yes, if we are committed to pre-evaluation, then the passed-through Can you still use any type of braces? Can I write |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett Thanks -- I feel like we're getting to the bottom of the two different ways of thinking about this space:
I should say that I find both of these perspectives reasonable. But I think it's worth distinguishing them in order to pinpoint the differences in feelings about the proposal. With Rust 1.0 the macro scoping system was indeed quite distinct from that for normal functions -- but that was seen at the time as a wart, and with the 2018 Edition we hope to finally bring macro scoping into harmony with the rest of the module system, so that you import/rename macros via the usual Once that happens, the scoping and dispatch for today's macros will work identically to that for free functions: to make an invocation, you need to unambiguously reference the function/macro, which is then dispatched to. For the macros-should-be-normal perspective, this is great -- it means that macros work just like functions in every respect where that makes sense. And if you take that as the starting point, then this RFC feels like a step backward, because it creates a new split, this time between macro methods and normal methods, that is not connected to the power/abilities of macros, but about "orthogonal" scope/dispatch rules. (I put "orthogonal" in scare quotes here because, from an implementation standpoint, interleaving macro expansion and type-checking is anything but!) To try to dig in further, what worries me here is:
For me, none of this translates to an absolute "no". The point is just that the mechanism proposed looks simple, but has complex and hard to gauge implications because of the divergence from standard dispatch. And for me, that suggests that we should:
|
This comment has been minimized.
This comment has been minimized.
|
@aturon Thank you for a well-thought-out analysis. I do think the distinction you draw makes sense; however, I would suggest unpacking the term "normal" and instead pinpointing specific ways that macros do and don't behave like other things, and which other things it behaves like. Without that, the blanket term "normal" creates a normative expectation that macros should behave like all other similar-looking language constructs, rather than carefully considering and adopting those expectations only when they make sense. (We already expect, for instance, that you can use macros in places you can't call functions, and that evaluation works completely differently, and those things should not change.) I think we need to separate two important questions. One is "should we do type-based dispatch at all", and the other is "should we only do type-based dispatch". For the former question, I'd say "maybe" with inclinations towards "no". For the latter, though, I'd say emphatically no. Another way to look at it: should we force macros to integrate with the type system and declare what type of thing they can operate on? I'm not suggesting that we should never provide the ability to integrate with the type system; I'm arguing that we should not force macros to integrate with the type system, when part of the point of a macro is to differ in that regard. When you declare a macro parameter of type I feel like you're looking at Suppose, hypothetically, that we could trivially support "typed" macro parameters, similar to function parameters. You could have a type associated with a macro "expr", and the macro would only accept expressions of that type. If we had such a feature, and didn't have backward compatibility concerns, would we say that you should only have type-restricted macros? Or would we say that it makes sense to give macros the power to operate on arbitrary expressions without declaring their type? Along the same lines, I'm arguing that there's value in a postfix macro system that does not declare the type of As an aside, I do think there'd be value in explicitly stating that this proposal benefits from the proposed changes to macro scoping and export/import. |
This comment has been minimized.
This comment has been minimized.
One of the things I really like about this proposal is that it doesn't treat I think, though, that it's this property of the proposal that also makes me feel like we should "go the full distance": impl MyConcreteType {
macro_rules! unwrap_or {
($self:self, $msg:expr) => ({
...
})
}
} |
This comment has been minimized.
This comment has been minimized.
|
@aturon But that doesn't "go the full distance", because inside that macro you can still do anything you want to macro_rules! assert_matches {
($self:self, $p:pat) => {{
match $self {
$p => {}
_ => { /* assertion failure */ }
}
}}
}(Please ignore the question of whether we want this specific macro, and treat it as a simple example of a class of possibilities.) What type is Along the same lines, consider a macro that would let you write Again, I'm not arguing that you shouldn't be able to have type-dispatched macros that only exist for a particular type. I'm arguing that you shouldn't only be able to have type-dispatched macros. |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett These macros look awesome! And they could even be chainable |
Centril
referenced this pull request
May 26, 2018
Open
Type ascription (tracking issue for RFC 803) #23416
This comment has been minimized.
This comment has been minimized.
warlord500
commented
May 26, 2018
|
actually, after thinking about it for a while, the only limitation, you have with post fix macros is that you can't put $self in a closure to make self not be pre-evaluated. |
This comment has been minimized.
This comment has been minimized.
I don't quite follow this. Assuming type-based dispatch, anything you did with
That's not necessarily true. You could imagine: trait AssertMatches {
macro assert_matches($self:self, $pat:pat);
}
impl<T> AssertMatches for T {
macro assert_matches($self:self, $pat:pat) { ... }
}Of course, bringing this into the realm of traits opens yet another can of worms :-) Anyway, I don't think we need to go deeper down this rabbit-hole just at the moment, and I think everyone would agree that type-dispatched macros are a looooong way off, if we ever did adopt them. On the whole, having sat with this proposal for a bit (and thanks to the discussion), I'm feeling increasingly comfortable with it. What I'd most like to see is a larger collection of worked examples, to get a feel for the impact on API design. |
This comment has been minimized.
This comment has been minimized.
|
Procedural note: one thing that's a bit tricky here is that multiple Lang Team members have reservations about this direction, but lack the time to fully engage on the thread to turn those intuitions into more concrete, actionable concerns. I think everyone agrees that this is out of scope for Rust 2018, and we've postponed a fair number of other RFCs on that basis, but haven't really set a clear policy (or declared an "impl period"). Moving to postpone feels like "stop energy", but the discussion here is somewhat of a distraction/source of stress for some folks on the team. @joshtriplett, do you have thoughts on how best to proceed here? I think the discussion so far has laid out some of the basic contours, but before we can go much further we'd need to hear more from others on the team. Would you be open to postponing until after we cut the Edition? |
This comment has been minimized.
This comment has been minimized.
|
It seemed at the outset of the RFC that part of the motivation was to get
ourselves a way to use await!() extensively with a better syntax, so that
would need to be ready for Rust 2018, but that was always an ambitious
timeline (if indeed that was intended at all).
…On Tue, May 29, 2018 at 8:08 PM, Aaron Turon ***@***.***> wrote:
*Procedural note*: one thing that's a bit tricky here is that multiple
Lang Team members have reservations about this direction, but lack the time
to fully engage on the thread to turn those intuitions into more concrete,
actionable concerns.
I think *everyone* agrees that this is out of scope for Rust 2018, and
we've postponed a fair number of other RFCs on that basis, but haven't
really set a clear policy (or declared an "impl period"). Moving to
postpone feels like "stop energy", but the discussion here is somewhat of a
distraction/source of stress for some folks on the team.
@joshtriplett <https://github.com/joshtriplett>, do you have thoughts on
how best to proceed here? I think the discussion so far has laid out some
of the basic contours, but before we can go much further we'd need to hear
more from others on the team. Would you be open to postponing until after
we cut the Edition?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2442 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAC3n7F2F_a8kATj740OMFXdBstBV2Afks5t3eMHgaJpZM4T_Cm9>
.
|
This comment has been minimized.
This comment has been minimized.
tmccombs
commented
May 30, 2018
|
My understanding wasn't so much that we need to have this for await, as that |
This comment has been minimized.
This comment has been minimized.
alexreg
commented
May 30, 2018
|
I don’t understand the fixation on Rust 2018. Surely it doesn’t hurt to have a go at a medium-priority feature like this, and if it’s not ready for stabilisation by the end of 2018, so be it (unlikely anyway). |
This comment has been minimized.
This comment has been minimized.
|
@alexreg It's all about focus. We have a lot of stuff in flight that we're trying to get polished up to ship with Rust 2018 (only a few months away), and at this point are primarily cutting scope, not widening it. While no single RFC eats that much time, collectively they can be a drain on the bandwidth of the various teams responsible for review. This is part of why we had the impl period last year. We haven't taken that step this year, but the basic issues are the same. |
This comment has been minimized.
This comment has been minimized.
alexreg
commented
May 30, 2018
|
@aturon Fair enough then. I suppose it depends who tackles this. Also, seems I was mistaken about the Rust 2018 deadline. :-) |
This comment has been minimized.
This comment has been minimized.
tmccombs
commented
Jun 3, 2018
|
Just throwing out another use case. This would allow defining something like: macro_rules! pipe {
($self:self, $f:path($args:tt) => { $f($self, $args) }
($self:self, $m:path!($args:tt) => { $m!($self, $args) }
}that would allow postfix style calling of free functions and macros that aren't defined to take self (using a differnt syntax than method calls). |
This comment has been minimized.
This comment has been minimized.
|
@tmccombs An example of usage: // Equivalent to corge(qux(bar(foo, 1, 2), 3))
foo.pipe!(bar(1, 2))
.pipe!(qux(3))
.pipe!(corge())This is a common macro in the Lisp world, e.g. Clojure has the ; Equivalent to (corge (qux (bar foo 1 2) 3))
(-> foo
(bar 1 2)
(qux 3)
(corge))Basically, it gives a "method-like" chaining syntax for regular functions, which often makes them a lot more readable. Since Rust prioritizes methods and traits so much, I'm not sure how useful a macro like that would be, but it's very nice in Lisps, since they tend to use functions a lot more. |
This comment has been minimized.
This comment has been minimized.
warlord500
commented
Jun 19, 2018
|
another, rather interesting use case for this is the try macro in the standard library could be useful for this. ? and postfix try though could start being serious contenders for propagating errors up the chain |
scottmcm
assigned
joshtriplett
Jul 5, 2018
This comment has been minimized.
This comment has been minimized.
mehcode
commented
Jul 27, 2018
|
Just thought of a use case that I'd love to see enabled by this. Was browsing RFCs and came across: #543 let fmt = "Hello, {}".to_string();
let s = fmt.format!("Bob");
assert_eq!(&s, "Hello, Bob"); |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Sep 16, 2018
|
Would it be possible to add |
stjepang
referenced this pull request
Oct 24, 2018
Closed
"Associated" macros / macros in impls / idk how to look this up or else I'd've found the existing issue #2577
Centril
added
A-macros
A-method-call
labels
Nov 22, 2018
petrochenkov
referenced this pull request
Jan 30, 2019
Closed
Shall we add a `for` for `macro_rules`? #57986
This comment has been minimized.
This comment has been minimized.
sagebind
commented
Mar 23, 2019
|
Here's another example inspired by a Reddit conversation today: macro_rules! with {
($self:self, $( _.$call:ident($($arg:expr),*); )*) => ({
$(
$self.$call($($arg)*);
)*
$self
})
}This is kind of replicates Kotlin's scope functions, which are a pretty cool way of replacing method chaining with something more flexible and readable. It might be used like this: let config = SomeConfig::default().with! {
_.foo("bar");
_.baz(42);
};The idea is that |
This comment has been minimized.
This comment has been minimized.
tikue
commented
Mar 23, 2019
•
|
Is there anything about with!(Config::default,
_.foo("bar");
_.baz(42);
);looks fine to me. |
This comment has been minimized.
This comment has been minimized.
sagebind
commented
Mar 23, 2019
|
Well the idea is to mutate some value and then return a value, so the first param isn't necessarily a constructor: let window = self.systems.get_mut<GfxBuilder>().with! {
_.resolution = (800, 600);
_.enable_vsync();
_.build_window()
};Seems clearer and easier to read than let window = with!(self.systems.get_mut<GfxBuilder>(), {
_.resolution = (800, 600);
_.enable_vsync();
_.build_window()
});Though I understand the viewpoint that the improvement is too minor to justify this feature. |
joshtriplett commentedMay 15, 2018
•
edited
This RFC introduces simple postfix macros, of the form
expr.ident!(),to make macro invocations more readable and maintainable in
left-to-right method chains.
In particular, this proposal will make it possible to write chains like
future().await!().further_computation().await!(), potentially with?interspersed as well; these read conveniently from left to right rather
than alternating between the right and left sides of the expression.
I believe this proposal will allow more in-depth experimentation in the
crate ecosystem with features that would otherwise require compiler
and language changes, such as introducing new postfix control-flow
mechanisms analogous to
?.Rendered