3 ms·
Yes, but having coded in both languages you can see how Go's concurrency system works well with it and fits neatly. Try using async in Rust and you'll see what
by null_investor 2y ago
Yes, but having coded in both languages you can see how Go's concurrency system works well with it and fits neatly.
Try using async in Rust and you'll see what I mean, it sucks to use it.
The same applies to cpp, you'll need years and years of experience to write somewhat decent cpp what you can write in Go with a few weeks learning coroutines, and will still be prone to make mistakes. I've done lots of cpp and can affirm that after decades I'm still not sure if my concurrent solution will run as well as my half-assed Go coroutine code.
Go coroutines is a good abstraction that is very fitting to the language, similar to the actor model and the BEAM VM (erlang/elixir).
Use the same abstraction in another language (like JVM or C) and you'll see how the devex makes a huge difference.
- littlestymaar 2y agoI'd chose async/await over threads any day, and many people do too, that's why this construct has gained so much popularity in many different languages. Async/await is a rare example in PL design where a new concept is implemented in one language and then adopted by many in the following years. In fact, the “threads are more ergonomic than async/await” seems to be more of an HN meme than a popular opinion in the wild. (And please don't quote What color is your function since it's mostly a rant about callback-based concurrency which everyone agree it sucks).
- cube2222 2y ago> since it's mostly a rant about callback-based concurrency which everyone agree it sucks It really isn’t, it’s a rant about there being two worlds (colors) of functions - async and not async, which don’t compose that well together. I believe Rust is exploring the ability to genericize functions over their asyncness, and I wish them luck in that endeavor. Async/await is, if anything, a tradeoff for performance and ffi over usability. As they are generally stackless and in the case of rust don’t need allocations. With green threads like in Go you’ll generally have to allocate at least a small stack for each goroutine, and you’ll have to fix up things for ffi, but it’s much more straightforward, all functions have the same color (you just write “blocking” code, but the underlying implementation is async), you don’t have an ecosystem split, and you don’t have to war with the type system every other day (as many feel like they’re doing in Rust when writing async). It’s a pretty good point in case that Java just spent a few years making project loom happen, which effectively brings the Go approach to Java. Async/await has its place, especially in very performance-critical code, but its usability is, IMO, miles behind threads. Of course, an ideal world would be green threads with rusts compile-time checking of concurrency safety…
- dgroshev 2y agoI'm not sure what you mean by "don't compose well". You can call sync code from an async function perfectly well. Going the other way around is either very simple in a throwaway code (just use pollster or tokio's block_on), or requires appropriate care (because async means IO and calling a function doing IO means your function now does IO too). I believe what most people are struggling with is having to think about doing IO instead of just doing IO wherever. But that's a feature, not a bug, and an overarching theme in Rust: you need to think what you're doing and how everything will work together. Yes you can't just put an async call somewhere down the stack without changing the signatures, but that's a good thing.
- LtdJorge 2y agoI think you mean block_on. Spawn blocking would be for your first use case, calling sync from async.
- dgroshev 2y agoYes, thank you, my bad. Fixed.
- cube2222 2y agoRust doesn’t model IO in terms of types though, does it? Sure, an async function likely does IO, but maybe it’s just receiving from a channel? At the same time 90% (completely made up number) of Rust just does plain blocking IO and you absolutely won’t get that in the signature either (other than looking into error types, that is). We’re not in Haskell here. You can just make a blocking call in your async code, not notice it does IO (or you’re just not proficient in async), and it falls to pieces, your runtime just hung. While “you can just use pollster” means you now have to wrap every function call to an async-first library in your async-less codebase. Again, this isn’t “composing well” in my book, it absolutely does create a divide in the ecosystem. While in Go all code is written in a blocking fashion, but all IO is done async (either via threadpool or non-blocking syscalls), handled by the runtime, because the threads are green. Of course, that’s less performant and falls apart if you start using C libraries that do blocking IO (but it’s not like Rust would do better in that latter case). At the same time, whether modeling IO in the type system is good or not, is I think something where opinions very much differ. To be clear, again, I’m not bashing Rust async in general, I just think it’s a tradeoff and I very much think its usability is worse than just writing straightforward blocking code.
- thadt 2y agoNot "threads are more ergonomic than async/await", but "threads and channels can be ergonomic". I've been a big fan and user of async/await in various languages for over a decade now, and it can absolutely improve reading sequential asynchronous logic flow, but lets not pretend it isn't without tradeoffs. Compared to wrangling synchronization contexts and function colors, channels can be rather straightforward.
- littlestymaar 2y agoErrors as return value are a “function color” too (in practice, you need to propagate it upwards the stack and it “contaminates” your code the same way as async/await). And in fact it interacts better with async/await (like in Rust) than with channels…