
A final proposal for Rust await syntax - gaogao
https://boats.gitlab.io/blog/post/await-decision/
======
jkarneges
I figure the reason the proposed syntax looks gross is because other languages
have been using a prefixed await for many years. The Rust decision seems well
thought out though, enough to make me wonder if perhaps other languages have
been doing it wrong.

My first experience with futures was in the form of QFuture
([https://doc.qt.io/qt-5/qfuture.html](https://doc.qt.io/qt-5/qfuture.html)),
and there you call .result() to block and wait for the result. This postfixing
is intuitive.

Rust's "?" operator is neat. I don't know if I've seen a postfix operator
anywhere else yet. It's certainly possible Rust got this language aspect
wrong, but as a user it feels pretty right.

Given the existence of the "?" operator, and the fact that futures resolution
via postfix is intuitive (and not a new thing either), IMHO the best course of
action probably is postfix. I'm not sure if literally ".await" is the best,
but it's in the ballpark.

~~~
arcticbull
IMO it's awkward for the following reasons:

(1) It looks like a field access, and field accesses are "cheap", method
invocations aren't. This breaks the 'conceptual weight' model when you look at
a line of code. Field accesses are simple and understood. It becomes
impossible to gauge the 'cost' of a line by looking at it unless you're
intimately familiar with the workings of 'await' and there's no visual cue.

(2) If we create new such postfix operators in the future we'll have to break
yet more source code by reserving yet more field names in all structs in all
existing code. This whole postfix thing is proving itself nice to work with so
I'd prefer a path of less destruction if we do lean more into it.

(3) It's a one-off that's different from everything else in the language
that's being shoehorned into existing syntax.

IMO, it's either break existing syntax by introducing something new or break
semantics of existing syntax. I'll always lean towards the former over the
latter. I like it being postfix, it's very much in the '?' family of
ergonomics which has shown itself quite nice to work with. I'd prefer postfix
"@await" or "!await".

~~~
majewsky
> there's no visual cue.

There is. Your editor is going to show the await keyword in a different color.
(Unless you happen to be Rob Pike.)

~~~
arcticbull
There's nothing else in Rust that requires me to use an editor for a semantic
cue, is there? Why start here? You are correct of course, it's more a question
of Rust being just as useable right now with or without syntax coloring, this
small change breaks a lot.

------
foota
It seems they've settled on using a postfix approach. I can't help but feel
this is a mistake. Rust is already a weird language to come to from the likes
of python, Java, or Javascript, and it feels to me like this relatively
unknown approach of putting the operator at the end is a mistake that will add
another confusing aspect of the language.

I feel like the committee has worried about the wrong things when considering
postfix vs prefix. Their main concern seemed to have been the extra syntax
required to make prefix work with . And ? (i.e., (await foo).bar() ), but
while avoiding this makes for a more optimized language I'm concerned they've
missed the impact this will have on new people.

Or maybe I'm just a Luddite :)

~~~
jwr
> Rust is already a weird language to come to from the likes of python, Java,
> or Javascript

Every time I see a statement like this, I remember a (paraphrased) statement
from Rich Hickey: "[musical] instruments are made for people who can play
them!".

I think unless you are specifically designing a beginner language (like
Scratch), you should not take into consideration "ease of use" or
"familiarity" arguments.

~~~
13415
I wouldn't use a programming language whose designer had this attitude.

~~~
jwr
Would you also refuse to play a musical instrument, because the designers had
this attitude? Pretty much all musical instruments are "hard to play" and you
have to learn them, sometimes for many years.

~~~
kazinator
Professional grade, expensive instruments are highly playable compared to the
junk.

------
jerf
"It has also devolved into a situation in which many commenters propose to
reverse previous decisions about the design of async/await on which we have
already established firm consensus, or otherwise introduced possibilities we
have considered and ruled out of scope for now. This has been a major learning
experience for us: one of the major goals of the “meta” working group will be
to improve the way groups working on long-term design projects communicate the
status of the design with everyone else."

I'll be pretty intrigued to see what they come up with for that. I've been up
closer to the Go developer's attempt to connect with the community, and while
I'm not going to say they've done everything perfectly on their end, I've also
been sort of frustrated by the way that they'll put up a request for comments
about something in particular, and a good chunk of the community replies
basically throw away _everything_ they're talking about and rewrite arbitrary
amounts of the language from scratch. By "everything", I don't just mean the
direct proposal under discussion, but the goals of the proposal, the
discussion of alternatives from other languages and how they are and are not
relevant, as alluded to above the things already proposed and rejected for
solid reasons, the context around the runtime and how it interacts with
that... that is, not just the proposal itself, but _all_ the thought and
reasoning around it too. That wasn't really the question, so to speak. This
frustrates everyone, on all sides, in various ways.

A lot of the problem is structural to what is being done; there's a lot of
impedance mismatch between the core designers and the community at large for
any language. It would be interesting to see some explicit thought around how
to address that, from a community with Rust's experiences.

~~~
zzzcpan
It's just means there is lack of trust to core designers. They haven't proven
themselves to users, that they are capable of addressing their problems and
align with their interests. Obviously core devs of Google backed language
cannot do better. They can only gain that trust from users within Google, not
outside of Google. Not sure about Rust though.

~~~
xpe
It does not necessarily mean a lack of trust. It could be different incentives
or priorities.

For example, some users might desire major redesigns of past work but not bear
the cost of the rework. As such, they might perceive some small improvement
without any (direct) cost.

Personally, I think some groups have moved so far in the direction of
community involvement that they forget the practical implications of
diversity. Leadership is hard for many reasons — one big reason is that
leading sometimes means making some people unhappy. Still, this is much better
than inaction.

~~~
pas
Could you please give some examples? I think I follow Rust pretty well, but
have no idea what/which-improvements/who/which-groups you could mean.

~~~
dj-wonk
I don't want to name any particular people or groups, because everyone makes
mistakes and has limitations.

I will say this: in my personal experience, I've been a part of groups that
struggle in dealing with complex decisions. Many times they get bogged down
when they don't find a clear answer that satisfies everyone or all criteria.
In many cases, such groups don't have a clear leader or the leader lacks the
skills, experience, and character to do what is necessary; namely, choose (and
communicate) the least-worst decision that keeps the ball moving forward.

In such cases, it is not necessary (and unrealistic to expect) that everyone
agree with every aspect of every decision. A leader needs confidence and
persistence to make tough decisions, as opposed to abdicating leadership. Some
examples of the latter include (1) ignoring a choice until some default
decision is made implicitly or (2) simply choosing the idea from the most
vocal person.

Put more broadly, in this context, leaders must balance four aspects: (a)
scoping and framing a decision; (b) gathering diverse points of view; (c)
building some degree of consensus or buy-in; and (d) making a decision. It
appears to me that the Rust language team handled all four comprehensively.

------
nixpulvis
> Its very easy to build a mental model of the period operator as simply the
> introduction of all of these various postfix syntaxes: field accesses,
> methods, and certain keyword operators like the await operation. (In this
> model, even the ? operator can be thought of as a modification on the period
> construct.)

I like this explanation.

But this whole post could really use some more concrete examples of the at the
very least the final style being used in various situations. Not just mainly
`expression.await`.

Keep up the work on this, I'm excited to use this in my projects.

~~~
Sharlin
My dislike for the ”dot keyword” syntax has been strong, but if it _is_
selected, hopefully it’s at least generalized to other keywords in the future,
so that `.await` doesn’t stay a lone awkward exception to what dot notation
means. In particular, I wouldn’t mind if in the future `expr?` could be
spelled `expr.try` for consistency (even at the expense of introducing
redundancy).

~~~
gamegoblin
I wonder if I would ever find something like

    
    
        my_iter
            .foo()
            .bar()
            .return
    
    

more appealing than

    
    
        return my_iter
            .foo()
            .bar();
    

I could see it happening, but I'll definitely need some time to get used to
it.

~~~
nixpulvis
See, now this is _exactly_ my issue with this syntax, I generally expect to be
able to "chain" things with the "." notation.

~~~
mintplant
You can chain .await, though.

    
    
        let n = future_of_future_of_int
            .await
            .await;

~~~
asdkhadsj
Curiously, will there be limitations to where it can be used? Eg, imagine a
`foos.iter().map(|f| f.await).collect::<Vec<_>>()`?

That seems crazy, think it'll be possible?

~~~
comex
Not as things are currently designed. This is similar to how you can’t use
things like the `?` operator, break, return, etc. inside a lambda and expect
it to affect the outer function: it doesn’t work because lambdas are treated
as their own functions. Personally I think it would be cool to pursue an
extension to lambdas that would allow some of those things to work, but I’m
not a Rust team member or anything, just an interested observer.

~~~
swsieber
Technically, `?` can work in lambda's if the return value is a `Result` -
though it won't work like it's being called as part of a normal loop or
whatever. That's largely mitigated by the combinators available on an
interator of `Result`s.

So I think we'll just need similar tooling for lambdas - perhaps an `async`
modifier for them? That (I think) would lift await stuff up to `?` in terms of
lambda support.

~~~
comex
Yep, and in fact async lambdas are already a thing on nightly. But, while I
may just be nitpicking, `.map(async |f| f.await)` wouldn't do anything useful.
Applied to an Iterator of Futures, it would be a no-op, kind of like (since
you mentioned `?`) `.map(|x| Ok(x?)). Instead you'd probably want some
combinator to turn it into whatever "Iterator but async" trait Rust eventually
standardizes on – futures-rs has Stream for this purpose, but std doesn't have
anything yet. That trait would have its own collect() method which would
return a Future<Output=Vec<T>>, and you'd then await on that.

Yeah, I guess I'm nitpicking.

~~~
swsieber
Thanks for the nitpick! I hadn't thought it through all the way. I was mostly
thinking about doing async stuff with the inner stuff, but you're definitely
right. I'm glad you commented because I've had to rethink it.

------
cyphar
Super excited to see async/await finally get close to MVP and landing. I am
not a huge fan of ".await", but there isn't much more to be said -- my
personal preference of "#await" or "@await" _does_ look like line-noise and I
think there's no perfect answer to this one (await{} was too messy and no
better than prefix-await, and (await foo))? had too many brackets).

I also appreciate that this proposal was insanely bike-shedded and so really,
any decision is better than no decision. I would've been happy with "await <<
f()" if it meant we could get this feature (lots of Rust projects I'm
interested in are waiting on async/await before focusing on further
development).

~~~
blattimwind
Wouldn't "await expr" or "await@expr" (if you hate whitespace) make more sense
compared to "expr await"?

    
    
        let db_conn = await pool.connect()
    

(Which is what e.g. Python uses, with the downside that you tend to need to
parenthize more because await has very low precedence).

vs

    
    
        let db_conn = pool.connect() await
    

.await is not soo bad, kinda method-y

    
    
        let db_conn = pool.connect().await

~~~
cyphar
I don't think "expr await" would be a good choice but that isn't the decision
that was made, and I wasn't arguing for it in the first place. TFA says that
using alternative punctuation was decided against because it would lead to
line-noise and I don't think there's much more to discuss -- I understand that
position and respect it.

My main issue with .await is that it does look method-y rather than keyword-y.
But while I like Python's "await expr" syntax _in Python_ the existence of ?
and chaining of methods in Rust justifies having a different syntax for it.

~~~
int_19h
Python also chains methods. And having to write (await (await (await
foo).bar()).baz()) is annoying.

~~~
imtringued
That doesn't look like well written async code at all... You're not supposed
to use await literally everywhere and especially not multiple times in a
single line.

Javascript already has an answer to this (Hint it was inspired by Monads but
it isn't one):

await foo.then(f => f.bar()).then(b => b.baz())

~~~
int_19h
What happens if f.bar() throws an exception asynchronously?

------
ch
Nice insight into the amount of thought going into this design proposal. It's
always tricky to introduce new syntax to a language and re-using the field
access notation here isn't as icky an approach as it first looks.

I don't know enough about rust to understand how this would operate during
compile time w.r.t if a user tries to define a field named 'async'. Is that no
longer allowed or would the compiler be able to disambiguate?

~~~
jmcomets
Disambiguation is easy when `await` is a keyword. [0] ;)

    
    
        error[E0721]: `await` is a keyword in the 2018 edition
        --> src/lib.rs:2:5
        |
        2 |     await: i32
        |     ^^^^^ help: you can use a raw identifier to stay compatible: `r#await`
    
    

[0] [https://play.rust-
lang.org/?version=stable&mode=debug&editio...](https://play.rust-
lang.org/?version=stable&mode=debug&edition=2018&gist=b3999aad31966f91af75d501358d02bd)

~~~
msla
Programming languages didn't used to have reserved words:

[https://en.wikipedia.org/wiki/Reserved_word](https://en.wikipedia.org/wiki/Reserved_word)

> Not all languages have the same numbers of reserved words. For example, Java
> (and other C derivatives) has a rather sparse complement of reserved
> words—approximately 50 – whereas COBOL has approximately 400. At the other
> end of the spectrum, pure Prolog and PL/I have none at all.

I don't really know why modern programming languages bother with reserved
words. Yes, it would be confusing to have a variable named 'if', but compared
to all of the other ways to write confusing code in, say, C, that's barely a
drop in the bucket. Plus, it's something good tooling (highlighting, for
example) could obviate, as it's entirely possible to use a grammar to show
exactly what role each token is playing in a statement.

~~~
sinistersnare
I don't know about PL/I but Prolog kind of cheats, in my opinion, about the
reserved words. It is very strict about naming conventions, which is (IMO)
worse than having reserved words.

~~~
msla
PL/I is syntactically like Algol, or Pascal, or C with more words and fewer
brackets. At heart, it's a "normal" block-structured procedural programming
language, which proves that a C clone could adopt the same basic idea.

~~~
int_19h
XQuery is probably a more recent example of that. No reserved words there,
just syntax.

[https://www.w3.org/TR/xquery-30/#nt-bnf](https://www.w3.org/TR/xquery-30/#nt-
bnf)

------
lukeqsee
I'm anxiously awaiting this.

I just finished overhauling a rather involved Tokio/Future based project, and
having await/async in the language spec will be a big step forward (and I'm
hoping the compiler errors improve as well).

As always, kudos to the Rust team for making decisions that are well-reasoned
and documented and in a way that looks to the future of the language (and not
just meeting a feature deadline)!

~~~
astrodust
If the way async/await has fundamentally changed how you write JavaScript code
is any example, this will be a tectonic shift, especially for event-driven
code like you use with Tokio.

~~~
kibwen
I agree that it's an important addition to the language, but I think it will
be less impactful for Rust than for JavaScript because server-side JS is
almost always in a domain that benefits from async IO, whereas Rust exists in
many domains where async IO isn't a performance priority.

~~~
astrodust
It's an important concurrency model. Perhaps Rust doesn't have a lot of async
code because it's been really annoying to do it. This could change that
dramatically.

------
jnetterf
While there might be a better syntax if you only care about this one feature,
async/await needs to fit into an existing language, and this syntax makes
sense with the rest of Rust.

This syntax makes it clear that’s it’s not a function or macro invocation,
works the same way as ‘?’, and allows for clear and concise chaining.

------
erikpukinskis
Reading this feels like a person with no good choices trying to convince
themselves there's no other way than their best bad one.

"This is the best proposal, except the syntax doesn't make sense, we don't
know if we can implement it, and conversation has broken down to the point
where we are running in circles and we don't expect to have any more ideas."

I am curious what the current way to do non-blocking code in Rust is and why
it's so bad that they'd introduce this much confusion to the language design
to fix it.

~~~
steveklabnik
If every choice was obvious, there would be no need for a designer in the
first place.

~~~
erikpukinskis
Who said any choice was obvious?

(Also would love your take on the question in the last line of my comment. My
gut as an outsider is that the best solution may be cultural not technical,
except Rust is struggling to implement cultural projects now that the
community is scaling fast. But you know what I don't.)

~~~
steveklabnik
Heres the most straightforward answer I can give:
[http://aturon.github.io/2018/04/24/async-
borrowing/](http://aturon.github.io/2018/04/24/async-borrowing/)

------
Grollicus
At this point I don't really care how the syntax looks. I just want to use it,
been waiting soo long for this.

Problems with differentiating .await from .member can easily be solved by
using syntax highlighting. Also, Rust is already strange to write, this is
just one more quirk one will get used to.

------
jules
The designers of Kotlin also had an interesting point: await (synchronous
call) should be the default, non-awaited (asynchronous) code should have a
keyword indicating that it is async.

~~~
Rusky
This would have been my favorite approach, and there was a discussion about
doing it in Rust: [https://internals.rust-lang.org/t/explicit-future-
constructi...](https://internals.rust-lang.org/t/explicit-future-construction-
implicit-await/7344)

There were two main objections:

First, a Rust async function like this (using explicit lifetime annotations
for exposition purposes only, normally they would be elided)...

    
    
        async fn f<'a>(r: &'a i32) -> i32 { ... *r ... }
    

...desugars to a sync function whose return value closes over its arguments:

    
    
       fn f<'a>(r: &'a i32) -> impl Future<Output = i32> + 'a { ... }
    

Sometimes you want the future to live longer than the arguments, so you write
that desguaring yourself:

    
    
        fn f<'a>(r: &'a i32) -> impl Future<Output = i32> {
            let i = *r; // Deal with the reference *now* before constructing the future.
            async { ... i ... }
        }
    

Ideally, functions could switch back and forth between these two forms
_without changing their API_ , for backwards compatibility reasons. This means
you can't just auto-await calls to `async fn`s like Kotlin does- it would need
to be a more complex rule.

Second, a lot of users want suspension points to show up in the program text
the same way `?` does. This is nice for managing invariants- you know when you
might be interrupted. (Personally I don't think this is a good reason; the
borrow checker and async-aware synchronization primitives would solve the
problem with less noise, but it is what it is.)

~~~
jules
I wonder about an altenate timeline where Rust kept its lightweight threads.
Marking async calls explicitly instead of marking synchronous calls with await
is a step in that direction, because that's also the syntax you have with
lightweight threads. What would problem 1 look like in that alternate
timeline?

In that case the concept of async functions disappears and your first function
becomes a normal function. The second function remains a Future building
function. So I'm tempted to conclude that this problem might be a non-problem,
caused by a confusion between async functions and Future building functions.
Even though an async function desugars to a Future building function, they are
conceptually distinct in the lightweight threads model. With lightweight
threads, all functions are async functions. A future building function
explicitly builds a delayed computation. The types _should_ be different.

An async function is just like a normal function, except that it may call
async APIs (i.e. other async functions). Calling an async function from a
normal function is an error; it does not return a future. The programmer never
sees that async functions are implemented with Futures under the hood. In
particular, an async function is _not_ syntactic sugar for wrapping its body
in an async block. We rename the async { ... } keyword to future { ... },
which constructs a future out of the block. You may call async functions in a
future block. So if you want to call an async function inside a normal
function, you must do future { foo() }, making it syntactically clear that the
call is delayed _even when the call is made from inside a normal function_.
The programmer no longer needs to think about how async functions work at all.
Don't tell them that future{ foo() } actually will just call foo(), and foo()
returns the future, they don't need to know that. The only thing they need to
remember is that async functions can only be called from within async
functions or future blocks. In all other respects they behave the same as
normal functions. All delaying of computation and running computation in
parallel is explicit.

IMO, problem 1 only occurs to programmers that have been told that async fn =
Future returning function. That's a leaky abstraction; it's syntactic sugar.
If you prevent them from developing this notion, the problem simply doesn't
occur. To understand the main proposal for async/await you basically have to
understand what desugaring the compiler is doing. With the "Explicit future
construction, implicit await" you can use async functions and futures without
understanding how they work under the hood. It's a non-leaky abstraction.

IMO, problem 2 is a problem for the IDE. The IDE can easily show which
function calls are async and which are not.

~~~
Rusky
I tried to lean pretty hard into "this syntax is just like threads" in that
internals.r-l.org post, when I wrote it, proposing almost exactly what you
describe here. Unfortunately problem #1 is not a result of confusion or
unnecessary conflation, but a fundamental question of lifetimes- the exact
same problem _already exists_ with normal OS threads just as it would with
lightweight threads.

That is, a function is always allowed to hold onto its arguments until it
returns. If its execution is deferred (e.g. `|| the_function(and, its,
arguments)`) for whatever reason (e.g. spawning a lightweight thread or async
task), the borrow checker has to consider that those arguments may stick
around indefinitely.

Of course, it is 100% doable to force people to work around this just by
giving future-building functions a different type. But as I described, this
means callers have to add or remove an extra `.run()`/`.await()`/etc. if the
API ever switches between the two. This is accepted in the world of threads,
but not in the world of futures, because _we already have a solution_ which is
"just switch to a future-building function, everyone's already awaiting it."

(Personally, while I certainly see it as a real problem, I would rather we
just live with it. It's not hard to work around, and we already do it in the
world of threads when necessary, which is rarely.)

~~~
jules
I still don't understand why #1 is a problem.

> But as I described, this means callers have to add or remove an extra
> `.run()`/`.await()`/etc. if the API ever switches between the two.

Switches between what though? When you want to do something asynchronously,
you indeed build a future and later .await() it. Suppose you then want to
build that future in a different way, for example by transforming future {
foo(x) } to making foo(x) itself return the future (i.e. moving the future{}
block inside foo), possibly because you want to dereference x before building
it. Well, the .await() was already there, and doesn't need to be changed. The
future{} ... await() pair gets introduced when you want to making things
asynchronous, which is exactly as it should be?

Furthermore, isn't that the same with the main async/await proposal? It is
indeed true that when you make things async you only have to mark a function
as async, and then all the calls to it automatically become async. However, at
the end of the day you still need to await those futures or else they won't do
anything. So when you switch from sync to async you still need to add those
awaits.

The difference seems to me the other way around: with the main proposal you
need _more_ awaits (namely, at all points where you want to stay synchronous).
With your proposal you need more async/future blocks (namely, at all points
where you want to switch to asynchronous).

I think that using the same keyword for async fn and async{} block is a source
of confusion, because it makes it seem like async fn is basically like
wrapping the body in an async{} block. It's what makes people think that an
async fn is like an automatically awaited future, which is a confusing way to
think about it and makes it hard to see why this proposal is a good idea (even
if it's actually implemented like that under the hood). I think it becomes a
lot clearer if you use a different word for these two concepts (like async fn
and future{} block), and remove the ::new() and only use future{} syntax.

This proposal does raise another question: why not just green threads, and
remove the concept of async functions entirely?

~~~
Rusky
> This proposal does raise another question: why not just green threads, and
> remove the concept of async functions entirely?

Making another reply because this is completely unrelated...

Rust already tried that. The problem is that Rust has a hard requirement as a
systems language to _support_ , at least, native I/O APIs, and the green
threads implementation added a pervasive cost to that support because all
standard library I/O had to go through the same machinery just in case it was
happening in a green thread.

That overhead made green threads themselves basically no faster than native
threads, so they were dropped before 1.0 to make room for a new solution to
come along eventually. Futures and async is that solution, and it turns out to
be much lighter weight than green threads ever could have been anyway- no
allocating stacks, no switching stacks, no interferering with normal I/O.

The syntax could have been different, but the implementation is far better
this way.

~~~
jules
Couldn't green threads in principle be implemented the same way as your async
proposal? The compiler could infer which functions need to be marked async. To
support separate compilation it might need to compile two versions of each
function, an async one and a normal one. You'd have exactly what you have in
your proposal, except you never have to write async fn. You could still have
blocking & non-blocking IO. It wouldn't totally unify green threads with OS
threads, but Futures/async/await don't do that either.

~~~
Rusky
Yes, though you probably wouldn't call them green threads anymore at that
point. (I mean, Rust async/await is implemented that way modulo syntax and
it's not called green threads. But that's beside the point.)

In fact Rust has already thrown out separate compilation with its
monomorphization-based generics, so making functions "async polymorphic" in
the same way wouldn't be anything new.

And while that's somewhat unlikely from what I can tell, Rust _is_ getting a
little bit of that "effect polymorphism" somewhere else- generic `const fn`s
can become runtime functions when their type arguments are non-const. So maybe
someday we'll be able to re-use generic functions in both sync and async
contexts depending on their type arguments.

------
arcticbull
After reviewing the syntax writeup, I've reached my own decision on what color
the bikeshed should be.

I like the "Unusual other syntaxes: Use a more unusual syntax which no other
construction in Rust uses, such future!await." [1] (or future@await). It makes
sense because it is in fact something different than what exists anywhere else
in Rust so it should receive a commensurate syntactic treatment. This was
written off pretty quickly in [1] but it appears the most consistent with
language philosophy -- specifically because it's inconsistent with any other
language features it should get inconsistent syntax. I think that's what's
throwing people off in this process: an attempt to impose 'consistency' on
something that just isn't. As such, it'll never feel natural to co-opt
existing syntax. Let's embrace it's inconsistency and introduce new syntax.

It supports '?' and '.' natively, and offers a path forward for new similar
kinds of postfix operators without making more field names off-limits -- even
the expr@match { .. } postfix expression thrown around would fit well. The
@-prefixed postfix operator would return a value like all other expressions.

tl;dr: Either the syntax is inconsistent or the semantics are, and IMO, the
former is preferable to the latter.

Either way, cool to see this feature moving along! Looking forward to using it
no matter how it's spelled out haha.

[1] [https://paper.dropbox.com/doc/Await-Syntax-Write-Up--
AcIbhZ1...](https://paper.dropbox.com/doc/Await-Syntax-Write-Up--
AcIbhZ1tPTCloXb2fmFpBTt~Ag-t9NlOSeI4RQ8AINsaSSyJ)

~~~
OtterCoder
I'd tend to agree with you, except for the fact that Rust reserved words are
reserved _everywhere_. Even if the syntax were changed, you still couldn't
name a field "async" or "return". Since it's no _extra_ burden on the
namespace, I'm less opposed to it.

I'm on the fence about disambiguation via unique punctuation, vs avoiding
Perl-like line noise.

------
mklein994
The syntax feels a bit Ruby-ish, doesn't it? Here's an example from the
article of how they could expand the syntax in the future:

    
    
      foo.bar(..).baz(..).match {
        Variant1 => { ... }
        Variant2(quux) => { ... }
      }
    

Compared to some Ruby:

    
    
      5.times {
        puts "Hello world!"
      }
    

Note I'm by no means an expert in Ruby or Rust, this is just what I thought of
when I saw the syntax.

~~~
hombre_fatal
A .match "method" just opens the dam to "why stop there?"

I prefer Kotlin's general approach of .let and similar:

    
    
        number = foo.bar().baz().let {
          match it {
            A => 1
            B => 2
          }
        }
    

Now anyone has the general tool for chaining without needing library authors
or language designers to create the API for them.

~~~
jnetterf
Neat! When working with Options/Results/Iterators, you can use `.map` [1] for
exactly this purpose. It would sometimes be convenient to have something like
`.map` / `.let` on unwrapped values as well.

[1] [https://doc.rust-
lang.org/std/option/enum.Option.html#method...](https://doc.rust-
lang.org/std/option/enum.Option.html#method.map), [https://doc.rust-
lang.org/std/result/enum.Result.html#method...](https://doc.rust-
lang.org/std/result/enum.Result.html#method.map), [https://doc.rust-
lang.org/std/iter/trait.Iterator.html#metho...](https://doc.rust-
lang.org/std/iter/trait.Iterator.html#method.map)

------
ketralnis
Here's a question that may sound a little naive but I'm behind on where the
Rust async stuff is shaping up and I don't know what to search for to answer
it.

You can of course already do async things in Rust the same way you do in C: by
passing around function pointers and either passing around a handle to the
event loop or, more commonly, using a single global handle. But you
theoretically could run multiple event loops (one per core, say) and could
pass around a pointer to which event loop you wanted to work with and a lot of
libevent examples do this. (IMO it's the only correct way to do it, but I have
sympathy for wanting to have the global variable.)

This is a little different to Javascript where the event loop is part of the
language/runtime. Javascript's "the event loop" model means you can't run more
than one event loop per runtime, so it's kind of nonsense to want to pass it
around explicitly.

Rust is more like C in this regard. So if I wanted to mix threads and events,
or run an event loop per core, or have an event loop that my Fancy HTTP
Library manages itself but exposes a blocking interface to, do I lose access
to the new fancy `.await` syntax? There's nowhere in this syntax to say which
event loop I'm registering myself with. Is there just one global one? Does it
get globally registered on runtime boot, and everyone references it? Or is
there a thread-local variable for the "current" event loop that this thread
knows about?

Even more concerning is how to make _other_ libraries do this than the one I'm
writing. If I'm calling into an async library how can I tell that library that
I want it to use "an" event loop instead of "the" event loop?

What should I be googling for to answer this? :)

~~~
steveklabnik
You do not lose access to it.

A short version of the answer is that at its core, you create a chain of
futures, and then place them on an executor. The executor is responsible for
executing them.

Async is sugar for creating a chain of futures. Doing that stuff is a property
of the executor. Completely different axis.

(And tokio today has a multi-threaded, work-stealing executor, for example.)

~~~
ketralnis
I see, so the "root" Future in the chain is bound to an executor and that's
where the "which event loop" part is specified

------
nchie
Wow, this is the only suggestion I really didn't like. I'm really happy that
it's progressing forward, but I can't help but feel that staying with the
macro and postponing the decision would've been a better choice.

A language should, in my opinion, not depend on syntax highlighting.

~~~
Arnavion
What good will postponing do? The blog post implies that it's already been
debated for a long time and all the arguments seem to have already been
brought forth. You can't just keep punting it forever; it has to get
stabilized at some point.

~~~
nchie
It's been debated, but there's hardly any code using it. After seeing how it's
being used by most people, it'd be easier to make a choice.

~~~
steveklabnik
Because it’s been clear that what exists is not stable, many people are
waiting until it’s stable to try.

There were a number of people who converted code to the various proposals, so
that helped.

------
holy_city
The process for how this feature is being handled is fantastic, and a great
example of what I love about Rust and its community.

------
andrewflnr
I'm sold. This post did a good job of addressing my concerns with the syntax,
plus a couple others. I'm now looking forward to postfix match.

I still kind of want postfix macros, though. I saw a couple other neat use
cases like `file.write!(...)`

~~~
Buttons840
User definable postfix macros might be nice, I don't know.

But we don't need user definable postfix macros to have a single
`expression.await!()` as a magical macro. There are already magical macros,
and to most Rust users `foo!()` means "magic here, read the docs". It would
have been syntactically consistent to have `await!()` also mean "magic here,
read the docs".

100% of Rust users will recognize the inconsistency of overloading field
access syntax. Only a portion of Rust users will dig deep enough into macros
to recognize that `expression.await!()` is special and is not defined as a
normal macro. By the time a user digs deep enough to realize that, they are
ready to understand why that inconsistency was necessary.

With `expression.await` new users may ask about the inconsistency and the
answer will be "because reasons you can't understand right now".

------
lachlan-sneff
I'm curious as to why the mandatory prefix syntax wasn't chosen (`await
{...}`). It's less magic than a magic field and it fits into preexisting
syntax better.

~~~
bvinc
I'm not sure why it wasn't chosen, but I can give you some arguments against
it. The braces introduce line noise. They introduce a new scope. I believe
that rustfmt will currently put a newline after the opening brace. And it has
the general problem that it can't be read from left to right.

(P.s. I'm not trying to start a debate, just trying to answer the question.)

------
wwwigham
I wonder why similarly to the existing `yield` keyword operator in the
experimental generators support doesn't rate highly in the decision making
here. Maybe they plan to revise generators to `expression.yield`, too? Also
`expression.return`?

Control flow from a dot-access rubs me just a bit the wrong way - honestly,
I'd rather the syntax be a bit cumbersome because if I saw multiple nested
`await`s in a single expression, I'd think the code was suspect and review it
more, while I'm probably not going to have quite the same reaction for
`x.await?.await?.foo.await?`, just because it _looks_ like normal property
chaining.

------
james-mcelwain
Really don't like the magic field access syntax.

~~~
apk-d
Having done most of my work in C# and TypeScript the Rust syntax felt _weird_
when I read about it, but on the other hand, I always felt bad being unable to
readably chain awaits, least they become

    
    
      var result = (await (await obj.DoSomething()).SomeOperation()).SomeValue;
    

In the end, all syntax is magic that you need to get used to, I guess.

~~~
wbl
Do notation solves that problem!

~~~
IngoBlechschmid
Came here to say that. I think do notation (and applicative idioms) are easily
one of the most-underappreciated features outside the Haskell community.

~~~
yawaramin
Scala has had monadic do-notation (called 'for-comprehensions') for a long
time and OCaml has recently gained both monadic and applicative 'do notation'
(called various things, but mostly 'let+ syntax'). Edit: Oh and F# has also
had computation expressions for a long time.

------
lucideer
This is way way off-topic but: I have never seen the word "postfix" used in
this way. "Suffix" is the normal/common English word for this as far as I've
always been aware.

A quick search has it listed it as a synonym, but I can't find any usage
outside tech and it sure seems like a tech-industry/coding erroneous neologism
trying to balance the seemingly logical "post" -vs- "pre".

Am I way off the mark here?

~~~
dj-wonk
Yes, "postfix" is commonly used. See
[http://www.cs.man.ac.uk/~pjj/cs212/fix.html](http://www.cs.man.ac.uk/~pjj/cs212/fix.html)
or
[https://en.wikipedia.org/wiki/Reverse_Polish_notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)

~~~
lucideer
Reverse polish notation is an entirely different usage: not the same meaning
as used in this proposal.

It's also commonly used as the name of a piece of email software, but that's
also an unrelated usage to this.

~~~
dj-wonk
No.

RPN and postfix notation refer to the same core idea. From Wikipedia: "Reverse
Polish notation (RPN), also known as Polish postfix notation or simply postfix
notation, is a mathematical notation in which operators follow their operands,
in contrast to Polish notation (PN), in which operators precede their
operands."

Can you please explain why you wrote "Reverse polish notation is an entirely
different usage"? With a citation, preferably.

~~~
lucideer
Reverse polish notation is a term used in mathematical notation for an a
alternative operator syntax to the more common "infix" notation.

The article describes affixing the word "await" to methods in a programming
language as a "postfix" and explicitly contrasts it with a "prefix", which is
a linguistic term for affixing words, commonly contrasted with the term
"suffix".

~~~
dj-wonk
I don't see any reference nor any clear argument supporting your claim that
RPN and postfix notation are different.

Again, RPN and postfix notation mean the same thing.

Here are some more references that show how these terms are commonly used:

1\.
[https://en.wikipedia.org/wiki/Infix_notation](https://en.wikipedia.org/wiki/Infix_notation)

2\.
[https://en.wikipedia.org/wiki/Polish_notation](https://en.wikipedia.org/wiki/Polish_notation)

3\.
[https://en.wikipedia.org/wiki/Postfix_notation](https://en.wikipedia.org/wiki/Postfix_notation)
(which redirects to a page on RPN, which is very strong evidence that your
claim is incorrect)

Ok, onto the next thing. You wrote:

> The article describes affixing the word "await" to methods in a programming
> language as a "postfix" and explicitly contrasts it with a "prefix", which
> is a linguistic term for affixing words, commonly contrasted with the term
> "suffix".

I'm not getting your point. I don't think you are summarizing the language
accurately. Perhaps you could quote the section of the article at length.

Here is one quote from the article: "The lang team proposes to add the await
operator to Rust using this syntax: `expression.await` This is what’s called
the “dot await” syntax: a postfix operator formed by the combination of a
period and the await keyword. We will not include any other syntax for the
await operator."

Here is another quote: "Our previous summary of the discussion focused most of
our attention on the prefix vs postfix question. In resolving this question,
there was a strong majority in the language team that preferred postfix
syntax. To be concrete: I am the only member of the language team that prefers
a prefix syntax. The primary argument in favor of postfix was its better
composability with methods and the ? operator."

These usages of "prefix" and "postfix" are consistent and idiomatic.

I hope this clears it up.

------
Dowwie
I trust that they've made the best decision.

------
jnordwick
They dismiss the best option - choosing another symbol instead of a period
with hardly a though because they don't lke "line noise".

Just as `?` was introduced, I don't see a good reason ("line noise" doesn't
even count as a thought) that they wouldn't introduce postfix notion with `@`
`#` `$` or any thing else similar.

If they are looking forward to expanding postfix synax for things like
`match`, then this would provide them with the most flexibility and
essentially provide a new namespace for those features.

"Line noise" isn't a reason. APL had it right in that consistent syntax (Perl
doens't count as consistent) is useful.

------
danfo
Since `await` is a keyword reserved for this purpose, could `(await
expression)` ever mean anything else? What is the cost of ripping off the
bandaid and starting there, as at least an alternative syntax that is
consistent with the language?

    
    
      match expression
      await expression
    

It feels like a future proposal, to allow these keywords to be chained more
conveniently with a postfix. It's cool that this can possibly be generalised
for chaining ergonomics.

    
    
      expression.match
      expression.await
    
      expression<-match
      expression<-await

~~~
kibwen
I expect a proposal of this nature to pop up relatively soon. If I had to
guess, I'd say the reason they're not doing this now is because they only want
to choose one option for the MVP, and they've decided that they prefer this
option to that option, and that the chosen option fortunately does not
preclude the future option.

Also, I would expect `await` to have mandatory braces, just as `match`, `if`,
`for`, etc. do.

------
trashface
As an old salty programmer, I don't understand why people are upset at this.
Sure the syntax is unusual, but they had good reasons for it, and people are
adaptable and get used to things.

Personally I like the fact that ".await" emphasizes that you are dealing with
something fundamentally different from other language constructs, because
futures _are_ fundamentally different.

------
cesarb
To me, this syntax feels like a "magic" method call (like in languages where a
field access can go through a getter method). That is, if you can think of
"x.await()" as a compiler-implemented method call on x which does some stuff
and returns a value (plus some magic to allow you to omit the parenthesis),
this syntax looks intuitive enough.

~~~
Sharlin
Except that, as described in the article, `await` is fundamentally a control
flow construct and too magical to be ever considered a ”sort of a method
call”. It’s not really a useful mental model. No function is supposed to be
able to reach outside its definition and _rewrite the control flow_ of its
caller.

------
Const-me
Language syntax and futures is only a half of the problem. IMO actual async IO
implementation is harder.

Too many incompatible platform-specific APIs: epoll and AIO on Linux, kqueue
on BSD and OSX, IOCP and new thread pool API on Windows. I’m pretty sure I
forgot couple others, and each of them have non-trivial amount of quirks,
bugs, and performance-ruining edge cases. Also these APIs are not directly
compatible to each other.

To be usable, async IO needs to be integrated into the rest of IO. You can’t
just place that on top, e.g. Java did that, IMO didn’t work particularly well.

The combination of the above makes creating good cross-platform abstraction
for async IO challenging.

Not saying impossible, but it’s very hard to do.

I’ve tried once in C++, for that project I needed epoll and iocp, but I wasn’t
able within reasonable time. Ended up swapping relatively large IO-related
parts of my app depending on platform.

~~~
bvinc
Most of the work you're talking about has been completed for a long time in
the crate "mio". It abstracts out all of the platform dependent async io
operations into a single consistent interface.

~~~
Const-me
Are you sure about that?

I've looked at the library, it doesn't support anything besides epoll and
IOCP, i.e. no BSD or OSX support, no AIO on Linux (the kernel one, not POSIX).

You probably don't want to consume IOCP API on Windows anymore, Vista
introduced higher level and easier to use StartThreadpoolIo and friends.

Also on Windows, even with IOCP, you don't want to split async IO from thread
pool. The OS kernel manages them both at the same time. Work stealing or other
custom scheduling is usually slower than what kernel does.

~~~
bvinc
It says that it is also backed by kqueue and supports FreeBSD , NetBSD, and OS
X.

I found this issue where they're discussing rewriting the Windows
implementation using the library `wepoll`.

[https://github.com/tokio-rs/gsoc/issues/3](https://github.com/tokio-
rs/gsoc/issues/3)

What would be the advantages of supporting AIO on Linux?

~~~
Const-me
> rewriting the Windows implementation using the library `wepoll`.

Interesting idea but I don’t like it too much. While removing the huge
complexity of manually managing IOCP and required resources, It’s an
undocumented API. New Vista+ threadpool-based IO also removes that complexity,
removes complexity of implementing thread pools in the higher level in Tokyo.
It’s documented and supported, and Rust has issues with WinXP support anyway.

> What would be the advantages of supporting AIO on Linux?

Faster async disk IO for the apps which are OK with the limitations, i.e.
which read/write complete blocks of O_DIRECT files. Databases come to mind.

BTW, io_uring feature coming into Linux kernel removes most limitations of AIO
while also improving performance.

------
eridius
It was glossed over why `await expr` isn't in the running; I take it it's
because the language team doesn't like the necessity of adding parentheses any
time you want to do postfix expressions on the await results e.g. `(await
foo()).bar()`?

~~~
Rusky
That was discussed in a previous post, but yes that's why. And in Rust, the
biggest postfix expression is the extremely common `?` operator, expected to
be used with _most_ futures.

~~~
eridius
I haven't really been following along very much. I take it from your comment
that await in Rust will not automatically propagate errors upwards like it
does in JavaScript? So you have to use `foo.await?` in the normal case if you
only want to handle successful results?

~~~
Rusky
That's correct. In a sense Javascript await isn't the thing that propagates
errors, that's just the language's usual exception behavior plumbed through
the Promise.

~~~
eridius
That's actually a really good point, I never thought of it that way.

------
arcticbull
Serious suggestion, and I assume you have -- have you considered ".await?" as
an alternative? It won't conflict with field names and cribs on the fact that
"?" changes control flow.

~~~
eridius
That conflicts with `.await` + the ? postfix operator. Namely, if `.await`
returns a Result, the ? operator could then be used like it is in code today.
In fact, this was referenced in the article:

> _The primary argument in favor of postfix was its better composability with
> methods and the ? operator._

~~~
arcticbull
Yeah, I thought that through after, and revised my thoughts. I wrote it up
top-level but I think the best way forward is embracing that this isn't
consistent with anything else in Rust and introducing new syntax, specifically
'!await' or '@await' postfix operator. The reason there isn't a good answer
that's consistent is that the behavior is inconsistent. As such, it needs new
syntax.

Either the syntax is inconsistent or the semantics are, and IMO, the former is
preferable to the latter.

------
danielscrubs
I'm very conflicted. In one hand I'm happy that becoming practical, on the
other hand: await and async is the best these awesome PhDs could come up with?
They where really in the forefront in so many regards that I kind of expected
something mindblowing.

------
marvelous
I wish more language constructs used postfix notation. It looks so natural to
me when reading from left to right. Consider:

[a+2 for a in as if a > 3]

Vs

as.filter(a -> a > 3).map(a -> a + 2)

Sometimes I even wonder why variable assignment has to precede the assigned
expression.

~~~
kibwen
_> Sometimes I even wonder why variable assignment has to precede the assigned
expression._

I think there's a good discussion to be had on preferring either prefix or
postfix operators, but the answer to this question (which is also the answer
to the question "why are we so accustomed to prefix keywords?") is easy:
because ALGOL did it.

------
cryptonector
Don't forget to consider dot-dot-await (EXP..await)as well.

------
lkjalkjsdfasds
Postfix works because Rust doesn't have piping or do-notation. I don't care
either way though, I'm welcoming await to the family & excited to use it.

------
mruts
Why doesn’t Rust just implement HKT and then make a Future a monad. It seems
to me that Rust has too many special things that arise from a lack of
expressiveness as it is.

HKT would make things considerably nicer.

~~~
steveklabnik
[https://twitter.com/withoutboats/status/1027702531361857536](https://twitter.com/withoutboats/status/1027702531361857536)

------
tav
In the off chance that some Rust developers are looking at this thread, I'd
like to put forward a counter-proposal:

1\. Use a postfix ?! instead of .await

2\. Use a postfix ?? instead of .await?

3\. Use the await keyword only in the "for await" construct

Then the common case becomes:

    
    
        let resp = http::get(url)??.to_string()
    

Which, imo, is a bit easier to parse than:

    
    
        let resp = http::get(url).await?.to_string()
    

This would make it easier to follow the core logic in async code, the same way
that ? made error handling so much cleaner in Rust.

~~~
afiori
I think the ?? can be already used for Result<Result<_,_>,_>.

I agree that the syntax should have `await' somewhere, and the observation
that it "clearly" is not a field access is actually credible to me. This also
open the possibility to have other kinds of postfix keyword e.g. .try or
.match

