Live data from Hacker News

Async and Await in Rust: a full proposal

boats.gitlab.io

91–100 of 196 posts

Re: Async and Await in Rust: a full proposal

#91
post #5

I'm waiting for the day when 'async' will be the default function type and 'await' will be the default type of function call. If anywhere down the callstack a function needs to await something it has to become an async function. And this needs to be done to the whole callstack recursively. So over time more and more functions of every codebase turn into async functions.

I think it should be possible to design an async system that is generically async , i.e. whether a given function with no special annotations or syntax is async (including all of the async-able calls that function makes transitively) is entirely determined by the top-level caller at compile time . The called function doesn't need any special syntax, and in fact doesn't even need to consider being async at all: the as…

To allow for separate compilation you need to compile each function twice though: a CPS version and 'classic' one. This can be wasteful. You also need annotations in the object file to describe the frame size of each CPS function. At some point this was seriously considered for C++ and I'm sure some language implementations actually do it.

Or you go with cactus stacks which have their own set of issues.

Re: Async and Await in Rust: a full proposal

#92
post #19

Earlier quoted context omitted.

I feel like you're making a very interesting point but it seems a bit broad and abstract for me to justify in my head. Could you clarify what you mean?

If you want easier concurrency with higher overhead, that's exactly what OS threads do. The OS kernel is the "event loop" in this case, and they're highly optimized for this, though there is some overhead which is hard to avoid. Rust has gone back and forth on this. Pre-1.0 versions of Rust had a "green threads" mode. It was removed in favor of OS threads because it wasn't worth the complexity. Go is a language in wh…

> Go is a language in which "all functions are async" and it's pretty cool.

Async has come to signify a very specific implementation strategy (i.e. stackless coroutines). Go definitely went in another direction.

Re: Async and Await in Rust: a full proposal

#93
post #39

Earlier quoted context omitted.

To pick some nits and generally elaborate, Erlang's VM also has a scheduler; it's not the presence or lack of a scheduler, it's how efficient it is and what guarantees it allows the programmer to make about their system. For example, OS schedulers are typically pre-emptive, which means your OS thread can get interrupted anywhere. On the otherhand, Go's scheduler (I'm using Go because I'm more familiar with it than wi…

>only allows context switching at well-defined points in your program. Erlang works the same way. The VM scheduler will only context switch on a function call. for or while loops don't exist in Erlang which means there is no risk of blocking the scheduler.

I assumed it must, I just wasn’t familiar. Thanks for clarifying!

Re: Async and Await in Rust: a full proposal

#94

This is exciting. It allows programmers to model their problems in code that fully accounts for the parallelism. For comparison, here is async/await in Zig: https://ziglang.org/documentation/master/#Coroutines Zig decided to go the other way - when you async call a function, it does eagerly evaluate until the first suspend point. This is less overhead than immediately suspending, plus it removes the dependency of the…

Can you explain "this is less overhead than immediately suspending"? AFAICT Rust's approach is as low-overhead as it gets, since the inherent separation between future creation and future execution means that future creation doesn't need to have any cost at all. I'm also not sure it makes sense to refer to Rust's behavior as "immediately suspending", since it's not suspending anything: until someone chooses to start executing the async function, there's no state to suspend?

As for the rationale for why Rust is aiming for this behavior, I think this comment buried in the RFC discussion (https://github.com/rust-lang/rfcs/pull/2394#issuecomment-382...) sums it up (and makes note of Dart 2.0 as a contrasting example):

"A fundamental difference between Rust's futures and those from other languages is that Rust's futures do not do anything unless polled. The whole system is built around this: for example, cancellation is dropping the future for precisely this reason. In contrast, in other languages, calling an async fn spins up a future that starts executing immediately."

"A point about this is that async & await in Rust are not inherently concurrent constructions. If you have a program that only uses async & await and no concurrency primitives, the code in your program will execute in a defined, statically known, linear order. Obviously, most programs will use some kind of concurrency to schedule multiple, concurrent tasks on the event loop, but they don't have to. What this means is that you can - trivially - locally guarantee the ordering of certain events, even if there is nonblocking IO performed in between them that you want to be asynchronous with some larger set of nonlocal events (e.g. you can strictly control ordering of events inside of a request handler, while being concurrent with many other request handlers, even on two sides of an await point)."

"This property gives Rust's async/await syntax the kind of local reasoning & low-level control that makes Rust what it is. Running up to the first await point would not inherently violate that - you'd still know when the code executed, it would just execute in two different places depending on whether it came before or after an await. However, I think the decision made by other languages to start executing immediately largely stems from their systems which immediately schedule a task concurrently when you call an async fn (for example, that's the impression of the underlying problem I got from the Dart 2.0 document)."

Re: Async and Await in Rust: a full proposal

#95

I'm somewhat disappointed that rust is going with async style stackless continuations. I'm a huge fan of stackfull coroutines/continuations as they are much more elegant and flexible. The downside is that they need a full stack, but I strongly believe (but can't prove) that rust has enough annotations and lifetime capabilities that it should be able to guarantee single frame allocation (or even no allocation for full…

We already have stackful coroutines. They're called threads. If for some reason you want M:N threading, we have that too, via the mioco library. (If you think mioco's M:N threading will provide far superior performance to regular 1:1 threads, though, you will probably be disappointed.)

If you look at the performance numbers of these approaches, you'll see why stackless coroutines are desired.

Re: Async and Await in Rust: a full proposal

#96
post #19

Earlier quoted context omitted.

I feel like you're making a very interesting point but it seems a bit broad and abstract for me to justify in my head. Could you clarify what you mean?

If you want easier concurrency with higher overhead, that's exactly what OS threads do. The OS kernel is the "event loop" in this case, and they're highly optimized for this, though there is some overhead which is hard to avoid. Rust has gone back and forth on this. Pre-1.0 versions of Rust had a "green threads" mode. It was removed in favor of OS threads because it wasn't worth the complexity. Go is a language in wh…

This is a very even-handed and fair description of the tradeoffs involved, something that is all-too-often missing in this space. Thanks for describing it so well.

Re: Async and Await in Rust: a full proposal

#97
post #88

Earlier quoted context omitted.

You can have userspace threads (or even hybrids m:n threading). They went out of fashion in the last decade for many reasons but were fairly common in the past.

I’m currently implementing threading into an editor scripting language. Could you talk more about this? It would be helpful.

The idea is that cooperatve userspace task switching can be faster than kernel space task switching (a handful if cycles vs thousands). So moving the scheduler in userspace seems a natural evolution. But now you lose the ability to run on multiple cpus as from the kernel point of view the application is a single thread. The next step is to run n userspace scheduers, for each hardware CPUs, each scheduler running a number of userspace threads (thus m:n). Effectively this creates a two level scheduler (one in the kernel one in userspace) and some OSs have custon APIs (look for scheduler activation) that allow the two schedulers to cooperate allowing full preemption of user threads in all circumstances.

The reason that m:n threading went out of style is that, for CPU bound tasks (where you want to run exactly as many threads as there are CPUs), it is just useless overhead, while for the hundreds of thiusands of IO bound threads scenarios, the cost of stack switching is dominated by cache misses anyway, and the cost of calling into the kernel is amortized by the fact that IO requires a call inti elevated privileges anyway. At the sametime kernel threads scheduling has become very fast and userspace threads, which requirea whole stack of their own are not significantly more lightweight than kernel threads.

The modern async model is a compromise. On one side, the 'threads' consist of a single stack frame are very light weight, on the other side there is no generic userspace scheduler, but scheduling is fully controlled by the application.

Re: Async and Await in Rust: a full proposal

#98

I'm somewhat disappointed that rust is going with async style stackless continuations. I'm a huge fan of stackfull coroutines/continuations as they are much more elegant and flexible. The downside is that they need a full stack, but I strongly believe (but can't prove) that rust has enough annotations and lifetime capabilities that it should be able to guarantee single frame allocation (or even no allocation for full…

We already have stackful coroutines. They're called threads. If for some reason you want M:N threading, we have that too, via the mioco library. (If you think mioco's M:N threading will provide far superior performance to regular 1:1 threads, though, you will probably be disappointed.) If you look at the performance numbers of these approaches, you'll see why stackless coroutines are desired.

I want stackfull coroutines, with custom, fast user space scheduling and task switching with guaranteed optimisation to a single stack frame and no allocation where possible.

I also want the ability to convert internal iterators to internal iterators with no overhead and even (especially) if the internal iteration function has not been specifically marked (i.e. no red/blue functions).

Hey, a man can dream.

Re: Async and Await in Rust: a full proposal

#99

Earlier quoted context omitted.

That’s all of this: https://doc.rust-lang.org/nightly/core/task/

That's an API. Like asyncio in Python is an API. If you make the event loop swapable without making this API mandatory however, it's never going to be a protocol.

The api is mandatory; it’s how async/await works.

Re: Async and Await in Rust: a full proposal

#100
post #5

I'm waiting for the day when 'async' will be the default function type and 'await' will be the default type of function call. If anywhere down the callstack a function needs to await something it has to become an async function. And this needs to be done to the whole callstack recursively. So over time more and more functions of every codebase turn into async functions.

I think it should be possible to design an async system that is generically async , i.e. whether a given function with no special annotations or syntax is async (including all of the async-able calls that function makes transitively) is entirely determined by the top-level caller at compile time . The called function doesn't need any special syntax, and in fact doesn't even need to consider being async at all: the as…

You’d still need to specify and/or have a default executer, neither of which are trivial tasks to design. Somewhat ironically, the go runtime is the closest to this and has the most explicit invocation of async behavior of the languages that have seriously tried to tackle this problem.
Post reply on HN