Live data from Hacker News

How Rust optimizes async/await

tmandry.gitlab.io

41–50 of 130 posts

Re: How Rust optimizes async/await

#41

Earlier quoted context omitted.

See these examples of how async/await simplifies looping and error handling: https://docs.rs/dtolnay/0.0.3/dtolnay/macro._01__await_a_min... . Await syntax makes it much easier to compose asynchronous code from different libraries, in the same way that ordinary function calls compose synchronous code. Talking to epoll/mio directly is certainly possible, but it's hard to make two different libraries work well together…

everybody always points to the 5 line basics, never to how complex async/await gets in a real system with timers, tuneouts, multiple errors types that need to be handled in different ways, heartbeats, side channel info, etc... It isn't better at that point. Cool, you can write a 5 line echo server easier, but good luck with a high performance server handling important transactions. (If there is documentation on how t…

The networking implementations I've had to do were all made a lot easier moving to async/await from hand-written state machines. It's a reduction in LOC terms of 6x or so, and the logic is much, much easier to follow.

State machines are viable if your transition function is such that effectively every combination of (state, input) leads to a different state. If the transition function is mostly describing "go to the (single) next state or error," then you're essentially asking the user to do a lot of bookkeeping that compilers are good at and users are bad at.

Re: How Rust optimizes async/await

#42

Earlier quoted context omitted.

They're not very well done. Rust kind of skipped the whole non-blocking thing and went straight to tokio, then had to backtrack a little to mio+tokio, then when mio is only half way jupmed to async/await. (mio was originall tokio rolled into it - it was the whole thing and you had to use mios sockets and mios timers on the mio event loop - tokio was then split out - metal io, it was not - this was because there wasnt…

Rust didn't implement async/await for fun. The team implemented it because it was perhaps the #1 requested feature from users, including me. You can see it here on HN. What's the #1 comment that always appears on Rust async threads? Invariably it's "why not just use goroutines? That kind of code is so much easier to write!" Now for various reasons M:N threading like in Go doesn't work in Rust. What does work, and off…

I originally came to rust because I was hoping to replace my c++ usage, but now Rust doesn't seem to care about that segment anymore and it tries to replace people's JavaScript usage. Has rust given on being as fast as c++ now?

So when does the GC go (back) in?

Re: How Rust optimizes async/await

#43

Earlier quoted context omitted.

Rust didn't implement async/await for fun. The team implemented it because it was perhaps the #1 requested feature from users, including me. You can see it here on HN. What's the #1 comment that always appears on Rust async threads? Invariably it's "why not just use goroutines? That kind of code is so much easier to write!" Now for various reasons M:N threading like in Go doesn't work in Rust. What does work, and off…

I originally came to rust because I was hoping to replace my c++ usage, but now Rust doesn't seem to care about that segment anymore and it tries to replace people's JavaScript usage. Has rust given on being as fast as c++ now? So when does the GC go (back) in?

If you think that Rust doesn't care about addressing the needs of C++ users, I'm not sure what to tell you.

It's not like C++ only focuses on features with zero overhead. The zero-overhead principle means that you don't pay for what you don't use. It doesn't mean that compiler developers are forbidden from working on any language or library feature that consumes more than zero cycles at runtime.

Re: How Rust optimizes async/await

#44

I don't quite follow. What exactly is the overhead that other languages have for futures that is eliminated here?

So for example, in Kotlin each piece of synchronous code within an async function is compiled into what is essentially a Java Runnable object, which must be allocated on the heap.

Re: How Rust optimizes async/await

#45

Earlier quoted context omitted.

See these examples of how async/await simplifies looping and error handling: https://docs.rs/dtolnay/0.0.3/dtolnay/macro._01__await_a_min... . Await syntax makes it much easier to compose asynchronous code from different libraries, in the same way that ordinary function calls compose synchronous code. Talking to epoll/mio directly is certainly possible, but it's hard to make two different libraries work well together…

everybody always points to the 5 line basics, never to how complex async/await gets in a real system with timers, tuneouts, multiple errors types that need to be handled in different ways, heartbeats, side channel info, etc... It isn't better at that point. Cool, you can write a 5 line echo server easier, but good luck with a high performance server handling important transactions. (If there is documentation on how t…

Having ported a full peer-to-peer engine in JavaScript from old-school callbacks to async/await, I strongly disagree with your comment.

Async/await really shines when the complexity arises.

Re: How Rust optimizes async/await

#46

Earlier quoted context omitted.

I originally came to rust because I was hoping to replace my c++ usage, but now Rust doesn't seem to care about that segment anymore and it tries to replace people's JavaScript usage. Has rust given on being as fast as c++ now? So when does the GC go (back) in?

If you think that Rust doesn't care about addressing the needs of C++ users, I'm not sure what to tell you. It's not like C++ only focuses on features with zero overhead. The zero-overhead principle means that you don't pay for what you don't use. It doesn't mean that compiler developers are forbidden from working on any language or library feature that consumes more than zero cycles at runtime.

So an opt-in GC would be considered zero-cost abstraction?

Re: How Rust optimizes async/await

#47

Earlier quoted context omitted.

If you think that Rust doesn't care about addressing the needs of C++ users, I'm not sure what to tell you. It's not like C++ only focuses on features with zero overhead. The zero-overhead principle means that you don't pay for what you don't use. It doesn't mean that compiler developers are forbidden from working on any language or library feature that consumes more than zero cycles at runtime.

So an opt-in GC would be considered zero-cost abstraction?

Sure. It's called Rc and Arc.

Re: How Rust optimizes async/await

#48
post #24

Earlier quoted context omitted.

Because most people don't like writing state machines by hand.

Pre-await Tokio code looks way worse to me.

A lot of that, I suspect, is that Rust's borrow checker makes specifying ownership correctly for callback chains extremely painful and unergonomic.

In my experience, closure-based async programming is only sufficiently painless in managed languages to be worth using. When you're dealing with manual memory management (or, rather, smart pointer memory management), you spend a lot more time trying to make the captures hold onto stuff.

Re: How Rust optimizes async/await

#49
post #10

I don't quite follow. What exactly is the overhead that other languages have for futures that is eliminated here?

Most languages allocate every future (and sub-future, and sub-sub-future) separately on the heap. This leads to some overhead, allocating and deallocating space to store our task state. In Rust, you can "inline" an entire chain of futures into a single heap allocation.

Has anyone ever done a comparison to see how much overhead this actually adds? I'd be really curious to see this represented in concrete terms.

Re: How Rust optimizes async/await

#50
This is one of the most concise tutorials on how generators, coroutines and futures/promises are related (from first principles) that I've seen.

I'm hopeful that eventually promises and async/await fade into history as a fad that turned out to be too unwieldy. I think that lightweight processes with no shared memory, connected by streams (the Erlang/Elixer, Go and Actor model) are the way to go. The advantage to using async/await seems to be to avoid the dynamic stack allocation of coroutines, which can probably be optimized away anyway. So I don't see a strong enough advantage in moving from blocking to nonblocking code. Or to rephrase, I don't see the advantage in moving from deterministic to nondeterministic code. I know that all of the edge cases in a promise chain can be handled, but I have yet to see it done well in deployed code. Which makes me think that it's probably untenable for the mainstream.

So I'd vote to add generators to the Rust spec in order to make coroutines possible, before I'd add futures/promises and async/await. But maybe they are all equivalent, so if we have one, we can make all of them, not sure.

Post reply on HN