I was under the impression that go isn't that far off from rust on most speed metrics being both compiled. So this adds complexity for what gain?
I was under the impression that go isn't that far off from rust on most speed metrics being both compiled. So this adds complexity for what gain?
But generally speaking, the reason I prefer Rust to Go for most developments is its type system. I could see myself using Rust to handle business-specific/safety-critical logic and Go as a fast prototyping language to assemble them.
Let's just say that the designers of Rust and Go have prioritized very different feature sets when designing their type systems. If you develop in Rust, it's typically because you want to write "fearless code", i.e. attach sophisticated invariants to types to avoid safety issues later in development/coming from client code. If you develop in Go, it's typically because you want "productivity", i.e. reach quickly a point where you code can run and you can start testing it.
As usual, it's a tradeoff. For most tasks, I happen to click better with the former. YMMV
There's also no way to declare a non-nil pointer nor to work cleanly with nested nil-able struct fields.
Naturally this is much better to type,
type Weekdays int
const (
Monday Weekdays = iota
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
than something like this, type Weekdays = (Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)
/sBusiness/safety critical logic tends to get complicated, and, at least in the case of business software, is more than likely is venturing into unexplored territory, so when you're not very skilled at programming finding the right design can become an insurmountable challenge. A more expressive type system offers a bandaid to work around that.
Yes, when I’m behind my desk I always find ways to make the business logic better and simpler and more fitting. But those damn humans never follow my rules.
The only way I see this happening for the same code snippet is if the Rust code was being compiled in debug mode instead of release mode.
Also, for some kinds of code (usually not CRUD), LLVM does better job than the Go compiler.
And if you do concurrency, Rust's async await can be faster than Go's approach (but it is a lot more complicated, and the perf gain usually doesn't matter).
Go has arguably a better concurrency model, so for some usecases it can work much better, but for some realtime programming stuff, Go is a no-go ba-dum-tss.
For webdev I would never chose Rust over Go, each language has their own advantage.
Go might have a better concurrency implementation, Go’s concurrency model is pretty much just threads and telling you to git gud scrub.
The developer experience on using it is much better than using coroutines in another lang.
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.
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).
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…
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.
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.
I also can't agree with the argument that if it's possible to hang the runtime there's no point in explicit async. Yes people can still make mistakes, but it's better when it's explicitly a mistake and not just haphazard IO everywhere being the norm.
What is the "async-less" codebase for? Why does it have to be "async-less"? A Go codebase is fully async at all times, so why shouldn't a Rust system extend async up to main() (or at least encapsulate IO-heavy parts in a separate async part)?
Runtime behaviour of async rust is ~equivalent to Go, so coming back to my original point, the difference is that 1) Rust allows to have non-async, predictable functions, async is opt-in 2) the opt-in must be explicit 3) you have more choice between intra- and inter-future concurrency (select! vs spawn). All those points are important and empowering, they aren't problems to be solved.
I agree that the flip side is that Rust forces explicit decisions upfront, but that's one of the core premises of Rust.
Except the composition problem only manifests itself badly when your “red function” is callback-based. Re-read the blog post and you'll see that the fundamental problem is that red functions are unwieldy (which it really is when you have callbacks, but not when you have async/await).
In Rust async/await is exactly as contaminating as `Option` or `Result`, or “error as return values” in Go or C, which creates the same split (functions which can propagate an error and the ones which don't), but it's almost never being discussed in terms of “function color” because it's not in fact such a big deal.
> I believe Rust is exploring the ability to genericize functions over their asyncness
In fact they (well, in practice mostly Yoshua) would like to genericize over all effects including fallibility because they are well aware that the problems are equivalent. The biggest problem in Rust's case being the combinatory explosion between different effects.
> 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…
This is entirely subjective: as I said above I would never willingly works with threads if I can use async/await instead. It's not just a performance thing at all. For many people async/await is just a superior user experience.
In fact it's exactly the same as exceptions vs `Result`: (unchecked) exceptions don't make colored functions whereas `Result` does, but exceptions, like threads hide where something can fail on your code (resp. when your code can get stuck waiting for IO) and this explicitness has value even if it comes with a cost in terms on annotation effort.
Telling someone that prefers async/await that thread is absolutely better is exactly like telling someone that dynamic typing is better than static typing because you have less things to when writing code…
Not only; the problem also manifests when you interoperate with blue functions, for example because they are provided to you by libraries.
The real problem came in JavaScript with the callback hell, everything else is a minor issue that nobody really notice.
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.