Live data from Hacker News

How to think about async/await in Rust

cliffle.com

61–70 of 268 posts

Re: How to think about async/await in Rust

#61
post #11

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

One advantage of async/await is that its easier to cancel things. For example, this leads to the design pattern where you have multiple futures and you want to select the one that finishes first and cancel the rest. In regular threaded programming, cancellation is a bit more painful as you need to have some type of cancellation token used each time the thread waits for something. This a) is more verbose and b) can le…

> One advantage of async/await is that its easier to cancel things. For example, this leads to the design pattern where you have multiple futures and you want to select the one that finishes first and cancel the rest.

> In regular threaded programming, cancellation is a bit more painful as you need to have some type of cancellation token used each time the thread waits for something. This a) is more verbose and b) can lead to bugs where you forget to implement the cancellation logic.

Yeah of course Rust just makes cancellation so easy by allowing the Futures to be dropped. What about the resources these Futures could have allocated within the context that are not just memory? You are saying it as if async/await somehow solved the whole problem of stack unwinding.

Re: How to think about async/await in Rust

#62
post #3

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Your comment seems to be conflating concurrency with parallelism. JS doesn't have any language-level abstractions for parallelism (async or not) but you do have Web Workers[0] and process forking (depending on runtime) to get actual parallel programming. JS async deals with concurrency, not parallelism. Threads are the opposite: They are interfaces for parallel programming and their use is orthogonal to how your appl…

> Threads [...] are interfaces for parallel programming

Threads are both for parallelism and for concurrency. Threads have been used for decades on machines without hardware parallelism.

> are you telling me you never write a mutex or implement locking logic

aside from the fact that mutexes vs futures is completely orthogonal to async vs threads, I definitely prefer dealing with mutexes. 99% of mutex usage is trivial and deadlocks are relatively easy to debug. The issue with locks is their performance implicaitons.

Re: How to think about async/await in Rust

#63

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Really weird to throw in JS as the culprit, this is a long standing issue, the asynchronous nature of web work probably highlights it but dealing with asynchronous tasks is part and parcel of writing complex performant applications. Much may be hidden by modern dev environments, but you won't get far beyond the most simple apps before you need to start thinking about how to deal with it.

The point is that other solutions were able to use traditional threads or green threads (via stack switching) to solve the same problem without the compiler having to transform sequential code into a state machine under the hood (since pre-emptive threads can be suspended and continued at any time, and green threads at specific 'yield-points').

Javascript is limited by the single-threaded browser runtime, and the way that WebWorkers were bolted on later didn't help much because WebWorkers with message passing are too inflexible to implement even green-thread-style task switching.

The state-machine code-transformation was indeed the only way out of this dilemma.

Async/await is essentially high-level language syntax sugar and it shouldn't matter whether it is implemented via code-transformation, or with green-threads or real threads, or a combination of all those under the hood (but in reality it matters because there's a difference between 'code slices' being scheduled on the same thread or different threads when dealing with shared resources).

Re: How to think about async/await in Rust

#64

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Async is, in many situations, better than traditional threads.

Threads are a resource hog. They take a lot of system resources, and so you usually want to have as few of them as possible. This is a problem for applications that could, in theory, support thousands of concurrent connections, if not more. With a basic thread-based model, you need 1 thread per connection, and if you have long-lived connections with infrequent traffic, those threads mostly do nothing but consume precious system resources. When you're waiting for data, the thread is blocked and does nothing. With async/await, you can have far fewer threads, maybe even just one, and handle blocking through a system call that wakes a thread up whenever any one of the currently blocked tasks is ready to progress. In languages with much lighter thread alternatives, such as Go's goroutines or Erlang's Beam processes, this problem basically doesn't exist, and so those languages don't need async/await at all.

Re: How to think about async/await in Rust

#65

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

This is an occurence of co-Blub paradox https://reasonablypolymorphic.com/blog/coblub/index.html

> And it’s not hard to see why; while humans have dedicated neural circuitry for natural language, it would be absurd to suggest there is dedicated neural circuitry for fiddling around with the semantics of pushing around arcane symbol abstractly encoded as electrical potentials over a conductive metal.

Huh? It's certainly the case that people who program have dedicated neural circuitry.

Most of the work is not being done by language centres[0]

[0] https://hub.jhu.edu/2020/12/17/brain-activity-while-reading-...

Re: How to think about async/await in Rust

#66

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Async is, in many situations, better than traditional threads. Threads are a resource hog. They take a lot of system resources, and so you usually want to have as few of them as possible. This is a problem for applications that could, in theory, support thousands of concurrent connections, if not more. With a basic thread-based model, you need 1 thread per connection, and if you have long-lived connections with infre…

> Threads are a resource hog.

Not really on any decent operating system, but if they are too heavy, there's still fibers aka green-threads aka stack-switching (which at least on Windows are an operating system primitive - but can be implemented in user code on any system that gives you direct access to the CPU stack and registers).

I doubt that the async-await state machine code transformation which 'slices' sequential function bodies into many small parts which are then jumped in and out frequently is any better for performance than stack-switching (in async/await you still need to switch a 'context pointer' on slice-entry/exit instead of the stack pointer).

One obvious advantage of the state-machine code transformation is that it also works in very limited single-threaded runtime environments without access to the callstack (like WASM).

In any case, from the user perspective, async/await should just be language syntax sugar, how it is implemented under the hood ideally shouldn't matter (e.g. it should also be possible to implement it on top of a task scheduler that runs on fibers or threads instead of a state-machine code transformation).

Re: How to think about async/await in Rust

#67

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

`epoll` was added to Linux in 2002.

Re: How to think about async/await in Rust

#68
post #31

Earlier quoted context omitted.

It wouldn't work. In Go, it doesn't matter how deeply nested a call to a blocking function is, when you do "go f()" the runtime takes care of things. With async however, if "await" is the default, then as soon as an async function calls another async function, it would block, completely defeating the point of async in the first place. I guess you could flip the rules and say that within an async function async is the…

It would just make things more explicit. Whenever you want to obtain a future you'd have to add "async". The execution of async stuff would work the same just instead of having to explicitly "await" things you'd have to explicitly "async" things. Of course you can't change the way Rust does async/await now without having to rewrite all the async code so not going to happen.

I don't think so, it would only mislead and hide what's really going on.

Creating a future in Rust does not have any side effects like running the future in background. This is not JS. Creating a future is just creating an object representing future (postponed) computation. There is nothing spawned on the executor. There are no special side effects (unless you code them explicitly). It works exactly as any other function returning a value, hence why should it be syntactically different?

If you called something that returned a future but you forgot to use the returned future - how is that different from e.g. opening a file for write and forgetting to write to it or from creating a User object and discarding it immediately, forgetting to save it to a database? There isn't really a difference, and therefore all those cases are handled by `#[must_use]` warning.

Contrary, an `await` is an effectful operation. It can potentialy do a lot - block execution for arbitrary long time, switch threads, do actual computation or I/O... So I really don't understand why you want to hide this one.

Maybe the naming is confusing - because `await` does not really just `await`. It runs the future till completion. You should think about it more as if it was named `run_until_complete` (although it is still not precise, as some part of that "running" might involve waiting).

Re: How to think about async/await in Rust

#69

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

Really weird to throw in JS as the culprit, this is a long standing issue, the asynchronous nature of web work probably highlights it but dealing with asynchronous tasks is part and parcel of writing complex performant applications. Much may be hidden by modern dev environments, but you won't get far beyond the most simple apps before you need to start thinking about how to deal with it.

Especially weird because the feature actually originates from C# and was only copied by JS later.

And in C#, you can absolutely combine it with Task.Run if you want it to run be concurrent. This feature made asynchronous operations so much better than all the patterns that came before it - and I think they goes for every language that ended up copying it.

Re: How to think about async/await in Rust

#70

Not specific to rust, but I think asynchronous programming in general is a hype. It didn't start because it is so awesome, it started because JS can't do parallel any other way. That's the long and short of it. People wanted to use JS in the backend for some reason. The backend requires concurrency. JS cannot do concurrency. Enter the event loop. Then enter some syntactic sugar for the event loop. And since JS is pop…

async/await allows to do concurrency without the need for explicit synchronization to shared data structures. E.g. I can do: loop { select! { _ = src_channel.readable() => src_channel.read(&mut buffer), _ = dst_channel.writable() => dst_channel.write(&mut buffer), } } without any mutex guarding the buffer, even though the reads and writes happen concurrently and share the same mutable buffer. This is possible because…

SCHED_FIFO
Post reply on HN