I am glad the team has been able to bring this issue to a reasonable conclusion.
Totally agree with the way you handled everything. I understand that a lot of people are probably unhappy with the final decision, but still as you said trade-offs + we can’t delay this forever and every time looking at different solutions and not making a decision. I honestly didn’t have any strong opinion on the syntax, but I did like the discussed once and I knew that the end one will be in the best of interest for the Rust language.
It was possible to delay the decision for a long time by adding a macro, and replacing macro with stable syntax any time later. ? operator was discussed and test driven for a long time while rust had try! macro.
As was pointed out in a previous thread, the cost of migration from try! to ? was nontrivial (small ecosystem split from people who were unhappy that their side of the syntax bikeshed “lost”, or simply did not have time to bother switching the code from one syntax to another, invalidation of old “best practices”…) and should not be overlooked when reviewing this past example.
Incorrect. A macro that influences control flow isn't possible without an underlying syntax to expand to:
macros can only influence control flow by expanding to code that the user could have written themselves
Macro could use any unstable syntax (even .await
), and that underlying syntax could be changed any time later.
#[allow_internal_unstable]
avoids leaking instability if necessary.
It's used pretty widely by stable standard library macros.
(EDIT: That's not a support for keeping await!(...)
from me, just a fact.)
The main reason we rejected using a macro is as @HadrienG said. However, I’d also note that all of the proposals that involve macros (including not only this one but also the proposals to have a prefix operator and introduce postfix macros) seem to ignore that macros cannot have keywords as names, and await
is intended to be a keyword. Obviously we could call this macro something other than await, but I notice that never even comes up when people propose a macro as such a simple and obvious solution.
As @petrochenkov mentioned, on the contrary this is often how standard library macros work. To give a concrete example, vec!
is definitely a stable macro but expands to definitely unstable syntax here.
This is at least partially complicated by the use of await!
as the nightly experimental syntax, so proposals naturally intuit that they can say "just do that", since it "already works".
Thus, I henceforth propose that all unstable std/core macros should be called bikeshed_placeholder_for_rfc_nnnnnn!
to avoid this issue. /s
As far as I can see, there are potential solutions to that. I'd be surprised if it weren't possible to hack the lexer and/or parser to treat await
as an identifier only when followed by !
. Alternately and mostly equivalently, we could make .await!()
a special syntax equivalent to prefix await
, and additionally add postfix macros with similar syntax to 'justify' .await!()
to Rust users. I am not actually advocating for those approaches; I like .await
better, and it's too late for further bikeshedding anyway. I'm just saying that I don't think the keyword issue would have been the hard blocker you're portraying it as.
This is absolutely possible and could be done by essentially extending https://github.com/rust-lang/rust/pull/60586/files#diff-da9d34ca1d0f6beee2838cf02e07345cR3024 to expect ! ( )
after .await
. All you need is essentially:
fn parse_dot_suffix(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
if self.span.rust_2018() && self.eat_keyword(keywords::Await) {
self.expect(&token::Not)?;
self.expect(&token::OpenDelim(token::Paren))?;
self.expect(&token::CloseDelim(token::Paren))?;
let span = lo.to(self.prev_span);
let await_expr = self.mk_expr(
span,
ExprKind::Await(ast::AwaitOrigin::FieldLike, self_arg),
ThinVec::new(),
);
return Ok(await_expr);
}
However, this also loses all integration with name resolution and so .await!()
is a macro in name only. At that point, given that .await
is a field in name only and .await()
is a method in name only, I think it simply comes down to .await?
being nicer to read and write than .await!()?
.
Or to put it more succinctly: An argument for can is not an argument for should.
Is there any way for user to implement await
keyword as prefix in program, for some people postfix looks strange, ugly and counterintuitive.
It’s very easy to write a macro:
macro_rules! wait {
($e:expr) => {
$e.await
}
}
Now you can use wait!(foo)
instead of foo.await
There’s even a crate which does this for you.
There’s also a crate which adds in implicit await (if you prefer that).
Sure. But it's very hard to write a macro which will be used by code not written by you. E. g. in third-party libraries.
You didn't get the point. A macro could be used to postpone syntax decision.
If .await
syntax is final, then the macro is pointless.
I don't understand your point. The macros are used exclusively inside of your code (specifically inside your async
functions).
It has absolutely no effect on anybody who is using your library, because it's an implementation detail.
In other words, there's literally no difference between using wait!(foo)
and foo.await
(other than the syntax)
No, that was not their point. They asked for a user-based way to use prefix await
, and I gave them one. They mentioned nothing about the official decision.
The decision has been made, it was carefully considered, and it will not be postponed. It's over.
No, it isn't pointless. People who prefer the wait!(foo)
syntax can use it instead of foo.await
.
That's the beautiful thing about macros: you can use them to change Rust to fit your personal preferences.
I read the source code of other libraries a lot. Sometimes I copy-paste the code. Sometimes I work with my colleagues how may find the idea of introducing a macro just to wrap the keyword not very smart.
It was just three weeks between prototype committed to rust repo and final decision. Other changes in Rust, especially major like this, bake for months in unstable. It was not carefully considered.
Sure.
But people who prefer single code style across the code base will use .await
syntax even if they prefer some other syntax.
This is not what macros are for. Macros are for extending rust, not for having a different syntax of already existing language constructs.
They could just as easily use wait!(...)
everywhere instead of .await
This is blatantly false, there was extensive discussion since the initial RFC for async/await. If you were late and only heard of this via the Rust Internals post a few weeks ago that's too bad (Rust cannot wait for literally everyone's opinion because new people come everyday), but you cannot say that there wasn't enough discussion.
edit: see @Pauan's comment below for a brief history of async/await
Actually, you can't really extend Rust with macros, you can remove boiler-plate (think lazy_static
) and make the syntax nicer (subjective, thing DSLs), but you can't extend Rust's capabilities.
You could remove macros from Rust entirely and not lose anything other than some efficiency where the macro wraps some nightly feature, but every non-std macro can be removed without restricting the language. Now, it would be significantly more painful to write Rust if you remove macros (because of lots of boilerplate for things like lazy_static
or similar), but you could do it.