Hacker News new | ask | show | jobs
by CJefferson 1583 days ago
Some people (including me) felt how async was added was worrying.

Adding ".await" means when I teach Rust we now need to say "x.y is member access. Unless y is 'await'. Then it's something totally different". That's the type of strange rule that C++ has, and if you gather enough of them, languages become very hard to teach.

However, async seems to be the only big language feature which effected many things which has been added.

3 comments

.await is a postfix keyword - it effectively means "crazy control-flow (monadic) magic is happening here". "?" for failure handling is another bit of postfix syntax, that means something very similar. A good IDE will show it with a different highlight, so that it will never be confused for a struct member or method call.

This stuff was discussed by the community for a long time - bikeshedding about syntax is a well-known "trap" in programming language design. It has turned out to be (IMHO) reasonably intuitive, and preferable to the alternatives.

My biggest problem with await is it's very non-obvious if you don't know to expect.

Seeing a '?' is obviously a "new thing", so people go look up what it is (in practice, I would expect any Rust programmer to learn '?' really early, but if they somehow miss learning it, they will know to go look it up).

I've seen beginners assume they were "missing the await member". Of course, once you know it's something new you can learn it. I would have prefered something like .!await, just to make clear it's a "new thing".

Isn't "?" just a sugar for something like the following? That's how I think about it in my mind at least.

  // with "?"
  let v = x?;

  // without "?"
  let v = match x {
    Ok(o) => Ok(o),
    Err(e) => return Err(e), // exit current scope due to the return
  }.unwrap();
That feels like something that could be done with a macro.
It's more like

  let v = match x {
    Ok(o) => o,
    Err(e) => return Err(e.into()),
  };
> That feels like something that could be done with a macro.

Indeed. It used to be a macro called try!.

Ah, thanks for the correction, that's better yes
Yes, that's control flow magic. It was prototyped with a macro, but failure handling comes up all the time in practical programming, and it's important to make it as ergonomic as possible.
I see. I think reading your initial comment I took issue with the hyperbole "crazy control-flow (monadic) magic is happening here".

But yes it is a hidden control flow, and taking in account hidden control flows is inherently more complicated (or at least has a steeper learning curve).

That's a fair point. I haven't touched async yet but I've seen lot of discussions regarding "await" and that seems to create a good amount of confusion. Still we are multiple magnitude away from the level of complexity C++ has.
Yeah, the await syntax is just bizarre. One of the worst eyesores of any languages I've personally encountered.

An await macro or function would have sufficed but because Rust is still mainly a hipster language with a hipster community, they had to choose a hipster syntax after years of bikeshedding about how to make it absolutely perfect.

Prefix syntax has drawbacks that were constantly pointed out in that long discussion. In particular, it doesn't compose well in practical scenarios. .await just 'flows' better and in a more intuituve way.
Completely agree. I've used it at work now for about a year and the postfix notation flows perfectly with more intricate usages of futures. Especially with the sense that you construct one, then decide when to await it, or await a collection or whatever. Following the rust style of immutable variables and chaining function calls.

You can even await the same future multiple times in a select statement keeping it executing a bit further each time until it returns, if you do it by mutable reference. Really quite magic.

Now we just need async closures and some nice async style iterators. Something like FuturesOrdered or even FuturesUnordered in an inline iterator style allowing efficient nice composability. Without any of the pitfalls and gotchas those two currently have.

I think that might be what throws many people off? The regular style of method chaining and having the cold futures just be a dead piece of memory you can pass around until actually passed to the runtime?

> An await macro or function would have sufficed

We tried, and it literally did not suffice.

Works in other languages fine.
Different languages have different semantics. The challenges here are related to the ways Rust works specifically, as well as its goals.