4 ms·
> Using a language for a large project without some kind of async/await notation seems painful. How bad is it using only combinators for async (assuming you wan
by notheguyouthink 9y ago
> Using a language for a large project without some kind of async/await notation seems painful. How bad is it using only combinators for async (assuming you want to stay on stable)? And is there a date yet when async/await will be stabilized?
Can someone explain to me what async/await is, in the context of a language like Go? I've used Rust a fair amount, but Go has been my native language for ~5 years now.
With that said, aren't both languages synchronous languages where you can drop into another thread (or green thread) whenever you want?
What UX is improved by async/await?
Eg, in Go I never felt the need for async/await (as I've used in JavaScript at least). If anything, Go's is better than async/await to me, because if I call `foo := bar()`, I don't have to know if `bar()` is an asynchronous function.. it just works. Is there something missing there?
Again, I use Rust and Go similarly in this post because Go is my frame of reference. I thought they were the same on this front, though. No?
What's missing here? What am I missing here?
- chatmasta 9y agoAsync/await makes sense in python or JavaScript because it’s syntactical sugar that improves readability/maintainability and mitigates against concurrency errors stemming from the event loop (JS) or the GIL (python). I haven’t worked with Go beyond playing with it a bit, but it seems async/await would be utterly useless in Go since goroutines already accomplish the same thing in a much more performant manner.
- eropple 9y agoC# was, as far as I'm aware, the first major language to implement async/await atop of Task<T> (akin to Java's Future<T>). The CLR uses native threads. Go channels are also orthogonal to async/await. Message passing is not a substitute for futures/tasks, though it can be used to achieve similar goals. I would be extremely cautious about claiming that Go channels would be "more performant" than an otherwise-equivalent futures implementation, too.
- wahern 9y agoGoroutines are not the same thing as Go channels.
- eropple 9y agoSorry, yes. Fibers (Go didn't invent them) also aren't directly equivalent because they don't allow explicit yields and composition.
- wahern 9y agoFibers don't, but goroutines + channels + closures do. They permit composition using the same call/return syntax and semantics as normal function calls. Futures and promises don't. Async/await is closer, but only by creating a second class of function incompatible with normal functions.
- deleted 9y ago[deleted]
- sagichmal 9y ago> Can someone explain to me what async/await is, in the context of a language like Go? As far as I know: c := make(chan int) go func() { c <- 1 }() // async i := <-c // await
- ehsanu1 9y agoGoroutines allow you to write async code ergonomically. Without those, you'd have to write callbacks, which is very unergonomic. Here's a before and after diff of some prototype async/await usage in Rust: https://github.com/mehcode/shio-rs/commit/8078a34c075bf2f52c0d6287f4ed0e3a912a3f7c#diff-924ded2c751ccd30e8769f459e61a362 https://github.com/mehcode/shio-rs/commit/8078a34c075bf2f52c...
- steveklabnik 9y agoYeah, you are missing some details. But this is a huge area, and it's hard to explain in a single HN comment. Basically, yes, the convenience of not needing to write async/await is useful, but in order to accomplish that, you need a bunch of other supporting decisions. These decisions make sense for Go, but do not make sense for Rust. The first thing to say on this topic is that virtually all of this (including async/await) is a library in Rust, not a part of the language. You can build whatever model you want on top. The one I'm describing is the futures/tokio model, which is becoming the de-facto, default one. But if you want something else, you can do it! This already is a divergence from Go, where you have what the runtime gives you, and that's it. Fundamentally, Go's model is cooperative coroutines, where the runtime does the pre-emption for you. (You can also call it yourself but as you note, this isn't usual.) It'll do this when you request something from the network, when you sleep, or when you use a channel. Tokio's model, on the other hand, is single-threaded. Instead of a ton of gorutines, there's an event loop, and you place a chain of futures onto it. Each chain of futures can sort of be thought of as a goroutine, but it's also different. For example, Rust can know at compile time exactly how big each futures' stack is, and so can do a single, exactly correct allocation. Anyway, each future in the chain handles yielding for you. Async/await, in Rust, is about making those chains easier to write. Anyway, I hope that helps. Maybe someone else will have a good comparison too. There's a lot of options in this space, and similar terminology that means the same, but slightly different things, so it can be hard to get your head around.
- Rusky 9y agoTechnically you can get the convenience of not needing to write async/await, with the same runtime implementation decisions underneath as Tokio/futures. Kotlin is an example of this approach- its coroutines are state machines but with implicit awaits. This can even be extended to "awaiting across function calls" with effect polymorphism. I haven't seen this done in this context, but it would allow things like passing an async closure, or a generic type with an async trait implementation, to a normal function and having it automatically become an async function instead. The real choice between explicit and implicit suspension points is thus syntactic, not implementation-driven. The reasoning there is more along the lines of "I like to see where my function might suspend" vs "I don't want to pepper my code with `await`."
- jabl 9y ago> Again, I use Rust and Go similarly in this post because Go is my frame of reference. I thought they were the same on this front, though. No? No. Rust used to have a green threads runtime like Go (and Erlang/BEAM, Haskell, etc.) but that was removed before the 1.0 release. So today, a thread in Rust is a heavyweight OS thread, like in C/Java and most mainstream languages.
- tytytytytytytyt 9y agoThe intro Rust book says you can easily find crates for green threads of you really want them. Is this not the case?
- tiuPapa 9y agoI am not entirely sure, it appears like due to the present status of std in rust, any green threads lib is prone to Undefined Behaviour whenever something uses Thread Local Storage.
- notheguyouthink 9y agoYea, I'm aware - but you can spin off a async execution and then send data back over channels (I forget what Rust calls), as well as wrap that whole thing up in a function with a return value so that the caller has no idea of the threaded execution taking place. Right? Sure the implementation of the threading differs greatly, but I was mainly referring to that I can spawn async behavior, and use synchronous blocking methods to get data back from that async behavior - AND encapsulate it. All of these are very different than, say, what JavaScript goes through anytime async is involved.
- steveklabnik 9y agoRust also has channels.
- lmm 9y agoasync/await means you mark all your yield points explicitly; rather than having the runtime implicitly preempt you whenever it chooses, you mark the points at which task switching can happen, and at every other point it's impossible (equivalently it's as though any block of code that doesn't contain an "await" were a critical section). The syntax strikes a nice balance, making these markers as lightweight as possible but no lighter: you don't want the yield points to be completely invisible, but you don't want them to distract too much from reading through the straight-through happy-path control flow. I find that having one implicit, pervasive, uncontrolled effect in a given piece of code is ok, but having multiple uncontrolled effects is very much not ok, because the ways those effects will interact can be very surprising. E.g. https://glyph.twistedmatrix.com/2014/02/unyielding.html https://glyph.twistedmatrix.com/2014/02/unyielding.html gives an argument for using explicit async/await rather than go-style implicit (green) threads, in terms of how task switching will interact with state mutation. You can make similar arguments in terms of how task switching interacts with error handling or logging or database transactions or... - one uncontrolled effect is fine, multiple uncontrolled effects cause chaos when they interact. Unfortunately the kind of examples where one can see multiple effects in a single application tend to be quite big by their very nature (and if your app is small enough to only need to have one kind of effect, then using a language in which that particular effect is uncontrolled is probably fine, perhaps even a good idea).
- dilap 9y agoHaving used both, I'd be pretty shocked if anyone found Twisted to be easier to reason about or less prone to bugs than Go...
- laurencerowe 9y agoTwisted predates async/await syntax in Python.
- ddorian43 9y agoIsn't gevent still better than async/await ? (if it had core support it would be, kinda like golang)