9 ms·
The comment threads here discuss async in many different languages: Rust, Go, JavaScript, Python. Can somebody knowledgeable describe how they are subtly differ
by tooltower 3y ago
The comment threads here discuss async in many different languages: Rust, Go, JavaScript, Python. Can somebody knowledgeable describe how they are subtly different between languages? Why are they painful in some but not in others?
Is there already an article that describes this well?
- vlovich123 3y agoThey’re painful in all contexts because of function coloring. They’re slightly less painful in Go and JS because there’s a single opinionated async runtime built in. In Rust they have yet to standardize a bunch of stuff that would remove the pain: async traits in std instead of each runtime having their own, a pluggable interface so that async in code doesn’t have to specify what runtime it’s being built against potentially an effect system to make different effects composable more easily (eg error effects + async effects) without needing to duplicate code to accomplish composition Keyword generics as the current thing being explored instead of an effect system to support composition of effects With these fixes async rust will get less annoying but it’s slow difficult work.
- yawaramin 3y agoThere's no function colouring in Go. Async functions don't have any special colour.
- SkiFire13 3y agoIMO saying there's no function coloring in a language ignores a lot of details. In Go there's no function coloring because there are only async functions. That's why they don't get any special color, they are the only color. In Go you don't get to use sync functions, which creates problems e.g. when you need to use FFI, because the C ABI is the exact opposite and doesn't have function coloring because it only allows you to use sync functions. Zig and Rust's async-generic initiative are a bit different in that they want to allow functions to be both sync and async at the same time. Ultimately there are still colors, but you don't have to choose one of them when you write a function. However IMO there are a lot of non-trivial problems to solve to get to that result. Ultimately Go's approach work well enough, and usually better than other approaches, until you need to do FFI or you need to produce a binary without a runtime (e.g. if you need to program a microcontroller)
- gpderetta 3y agoThat's a bit of nonsense. As far as I know, all functions are sync in go. The fact that they are implemented async in the runtime with an user-space scheduler is irrelevant (you could otherwise make the point that there are truly no sync functions). If we call the go programming model async, the word has completely lost all meanings.
- SkiFire13 3y agoWhat is the difference between a sync and an async function for you then?
- gpderetta 3y agoAn async function is on CPS form and return it's result via a return continuation. Typically when invoked from a non CPS function it also forks the thread of execution. These days async functions are also typically lazily evaluated via partial evaluation and the return continuation is not necessarily provided at the call site. A sync function provides it's result via the normal return path.
- 65a 3y agoGo works fine for C FFI, all of its problems there are caused by its innovation wrt dynamic stack sizes and having a garbage collector. I'd rather write multithreaded Go FFI than deal with JNI again, anyway. There isn't really a language keyword-level concept of async in Go that's comparable to `await` in JS, or Java futures, or Rust async.
- yawaramin 3y ago> IMO saying there's no function coloring in a language ignores a lot of details. Details which are meant to be ignored. When you use async/await constructs in various languages, you don't care about the fact that they are desugared into callback chains under the hood. You either do async/await in a language or you don't. That's what the concept of 'your function has a colour' means. If you want to change the meaning, OK but then you're talking about something else.
- anonymoushn 3y agoGo doesn't have function coloring. Greenlet, Lua, and libco solve the problem without function coloring by adding a stack-switching primitive. Zig solves the problem without forcing function coloring on all consumers of functions by having the compiler monomorphize functions based on whether they end up being able to suspend.
- lifthrasiir 3y agoA more accurate description would be that Go has a single function color, that is namely green. This distinction is important because, for example, C also has no function coloring problem only because it doesn't care about lightweight threading, i.e. its function color is always red. Only Zig's approach, and hopefully Rust's keyword generics if accepted, can be considered to have no function color.
- thiht 3y agoI don’t understand the difference between "single color" and "no color", can you explain? What makes Zig’s approach colorless?
- lifthrasiir 3y agoWhile the original use of "function colors" was purely syntactic [1], they can be easily remapped to cooperative vs. preemptitive multitasking. This remapping is important because they change programmer's mental model. For example, the common form of `await` calls implies cooperative multitasking and people will have a good reason to believe that no other tasks can't affect your code between two `await` calls. This is not generally true (e.g. Rust), but is indeed true for some languages like JS. Now consider two variants of JS, where both had `await` removed but one retains cooperative multitasking and another allows preemptitive tasks. They will necessarily demand different mental models, even though it is no longer syntactically distinguishable. I believe this distinction is important enough that they still have to be considered to have a function color, which is only uniform within a single language. Zig's approach in comparison is often called "color-blind", because while it provides `async` and `await`, those keywords only change the return type to a promise (Zig term: async frame) and do not guarantee that it will do anything different. Instead, users are given the switch so that most libraries are expected to work equally well regardless of that switch. You can alternatively think this as follows: all Zig modules are implicitly parametrized via an implicit `io_mode` parameter, which affect the meaning of `async` and `await` and propagate to nested dependencies. There is definitely a color here, but it's no longer a function color because functions can no longer paint themselves. So I think it's reasonable to call this to have no function color. [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- pcwalton 3y agoFor Go I'd say there's a single synchronous runtime built-in. People say that Go is async because the implementation of goroutines is async internally, but the implementation of threads on every OS is async internally too. The only real difference as far as sync/async is concerned† between goroutines and threads is that Go's implementation of goroutines is in userspace, while the implementation of OS threads is in kernel space. Both are equally asynchronous under the hood. † Yes, there are other differences between goroutines and typical OS threads, such as stack sizes, but I'm only talking about I/O differences here.
- boustrophedon 3y agoI think the biggest underlying difference is that Rust does not have a language runtime, whereas the other three you've listed do. Since the language runtime can preempt your code at any time, it becomes a lot easier to make async work - at the expense that now data races are easier to create. I'm not going to pretend I'm an expert but would be happy if someone could expand further.
- hedgehog 3y agoIn the early days of Rust there was a debate about whether to support "green threads" and in doing that require runtime support. It was actually implemented and included for a time but it creates problems when trying to do library or embedded code. At the time Go for example chose to go that route, and it was both nice (goroutines are nice to write and well supported) and expensive (effectively requires GC etc). I don't remember the details but there is a Rust RFC from when they removed green threads: https://github.com/rust-lang/rfcs/blob/0806be4f282144cfcd55b1d20284b43f87cbe1c6/text/0230-remove-runtime.md https://github.com/rust-lang/rfcs/blob/0806be4f282144cfcd55b...
- deleted 3y ago[deleted]
- steveklabnik 3y agoI gave two talks about this: * An overview of terminology, and a description of how various languages fit into the various parts of the design space https://www.infoq.com/presentations/rust-2019/ https://www.infoq.com/presentations/rust-2019/ * A deep dive into what Rust does https://www.infoq.com/presentations/rust-async-await/ https://www.infoq.com/presentations/rust-async-await/
- Too 3y agoJS makes it easier because it was always single threaded and never had any sync IO to begin with. This means, before async existed, any library doing IO had to be based on callbacks. Then came Promises, which are essentially glorified callbacks and then came async which can be seen syntax sugar for Promises. So you will never see synchronous code that depends on an asynchronous result. The concept of sync code waiting for something just never existed in JavaScript. Instead you wake up your sync functions with Promise.then()-callbacks and that same mechanism bridges async functions. It’s also very rare to have compute heavy sync code in JS so there is rarely any need to run it multi threaded.
- penteract 3y ago> The concept of sync code waiting for something just never existed in JavaScript. Have you forgotten prompt() and friends?