News for hackers. Stuff that matters.
Re:Await in Rust by atombender on Monday August 12, 2019 @12:11PM (#20676770)
I agree. I raised the topic recently on HN here [1], but didn't get much of a response.

One of the problems with async is that it's viral. Make a function async and your entire call graph has to be made async, unless they handle the future manually without await. Async introduces a new "colour" to the language.

I absolutely agree that asynchronicity should be the provenance of the caller. If you look at typical code in languages with async/await such as JavaScript and C#, awaiting is the common case. So if async causes your code to be littered with awaits anyway, it makes more sense to make awaiting the default (just like it's the default for the rest of the language) and "deferring" the exception. Call it "defer" or something.

(As an aside, I was always disappointed that Go's "go" doesn't return a future, or that indeed there's no "future" support in the standard library. Instead you have to muck about with WaitGroup and ErrGroup and channels, which introduces sequencing even in places that don't need it. Sometimes you just want to spawn N goroutines and then collect all their results in whatever order, and short-circuit then if one of them fails. The inability to forcibly terminate goroutines is another wart here, requiring Context cancellation and careful context management to get right.)

[1] https://news.ycombinator.com/item?id=20405124

Re:Await in Rust by fluffything on Monday August 12, 2019 @12:21PM (#20676879)
> One of the problems with async is that it's viral. Make a function async and your entire call graph has to be made async

In most languages, including Rust, async functions can call synchronous functions, so this isn't entirely true.

> unless they handle the future manually without await.

At some point, the futures are always handled manually, e.g., via the run-time, or in Rust case, by scheduling on an Executor, which is nothing but a fancy name for a library that manually handles a lot of futures.

> If you look at typical code in languages with async/await such as JavaScript and C#, awaiting is the common case.

Do you have any example of code that uses await more than synchronous functions ?

> So if async causes your code to be littered with awaits anyway, it makes more sense to make awaiting the default (just like it's the default for the rest of the language) and "deferring" the exception. Call it "defer" or something.

If the purpose is just to change the defaults, then you'd need `defer vec.len()` to access the length of a vector, or `defer array[i]` to index into an array, or `defer obj.foo` to access the field `foo` of an object, ... unless you want to make all of those operations `async`.

If you make await the default and add a `defer` operation that doesn't awaits, then it's hard to imagine how the resulting code could contain less `defer` annotations than `await` ones.

At that point you might choose one of the many non-zero-cost solutions to removing the async distinction, and call it a day like Go does.

Re:Await in Rust by atombender on Monday August 12, 2019 @02:45PM (#20678527)
I feel like you misunderstood several of my points.

First, async infects the call graph, that is, going up the stack. Not down.

Secondly, by "manually" I mean using the future API -- and_then() and so forth.

Thirdly, "defer vec.len()" doesn't make sense unless you want to execute it asynchronously and get a future. And that doesn't make sense unless you know it to be an expensive operation you want to run concurrently. I'm proposing that the hypothetical "defer" would act like Go's "go", except it would return a future.

Lastly, my point was that most code is serial and relies on awaits. For example, if you're doing a name lookup, then opening a socket, then writing to it -- three async calls that must be serialized with await because the actions depend on each other. Not awaiting is needed when you intend to use the future for some purpose, such as adding to a queue or putting in a map, or perhaps you're doing N connections that you put in a vector to ask the runtime to wait for all of them (does Rust's await syntax support awaiting multiple futures?). But most high-level "user" code doesn't juggle futures, it just awaits whenever it needs a result. From a language-ergonomics perspective, it makes more sense to optimize for the common case.

I've never written any async/await code in Rust, but I've written plenty in JavaScript, and it's the same story.

Re:Await in Rust by steveklabnik on Monday August 12, 2019 @03:05PM (#20678759)
> does Rust's await syntax support awaiting multiple futures?

No, but you use a join combinator, which itself returns a future, that you can then await.

Re:Await in Rust by atombender on Monday August 12, 2019 @05:40PM (#20680333)
> Do you have any example of code that uses await more than synchronous functions ?

But that was not what I was arguing. Rather, I was arguing that await is more common than capturing the raw future. Case in point: The article, where all of the "real code" snippets rely purely on await.

What I proposed was that async functions would always be automatically awaited unless invoked with "defer", because most code is expressed as a series of invocations that use the return value of the function call.

There are times you want to store the raw future as some kind of state (for example, in a GraphQL server you might "resolve" the GraphQL tree as futures), or wait for multiple futures, or similar, but those are, in my experience, the exceptions, not the norm. Most code is:

  let v1 = do1().await;
  let v2 = do2(v1).await;
  // etc.
In other words, serial.
Re:Await in Rust by Maledictus on Monday August 12, 2019 @02:21PM (#20678306)
Maybe only await functions by default that are await and normally call functions that are not?
Re:Await in Rust by Rusky on Monday August 12, 2019 @04:15PM (#20679474)
> Make a function async and your entire call graph has to be made async, unless they handle the future manually without await.

You can "handle the future manually without await" very easily, it's just one function call. The same sort of thing is available in most other languages with async/await- you can prevent async from going up the call stack by synchronously blocking on a future instead of awaiting it.

The caller is absolutely in charge of whether something runs asynchronously. The only difference is a syntactic default- and in fact some languages even make async functions calls default to synchronous execution. For example take a look at Kotlin's `suspend fun`s.

Re:Await in Rust by laughinghan on Tuesday August 13, 2019 @10:04PM (#20692112)
One of the problems with async is that it's viral. Make a function async and your entire call graph has to be made async, unless they handle the future manually without await. Async introduces a new "colour" to the language.

Not in Rust. Or C#. Or any language except JavaScript.

In Rust, a synchronous function can call and block on an async function with executor::block_on(). C# has Task.Wait() and Task.Result(). Python 3 has asyncio.run(). Scala has Await.ready() and Await.result(). Kotlin has runBlocking {}.